Deploy your first app
This guide walks you through taking a GitHub-hosted application from source code to a running, publicly accessible service on MCTL. It covers both the portal-based path and the MCP-based path for each step.
By the end you will have a live service URL in the form https://<team>-<service>.mctl.ai with a passing health check.
Managing MCTL through MCP
MCTL can also be managed through MCP-compatible AI tools such as Claude, Cursor, and VS Code. You can use MCP to inspect tenants, deploy services, check workflow status, and operate the platform conversationally. Each step in this guide includes both a portal path and an MCP path.
To use the MCP path, connect your AI client first: Connecting to MCTL MCP.
Before you begin
- A GitHub account with push access to your application repository.
- A provisioned MCTL tenant with working platform sign-in and developer access. If you do not have one yet, request one at mctl.ai or follow the instructions in the Quick Start guide.
- An AI client connected to MCTL via MCP if you intend to use the MCP path. See Connecting for setup instructions.
Step 1: Choose or confirm your tenant
Every service belongs to a tenant (a Kubernetes namespace with its own resource quotas and access controls). Confirm which tenant you will deploy to before proceeding.
Via the portal
Sign in at app.mctl.ai. Your tenants are listed on the dashboard. Note the team name — you will use it throughout this guide.
Via MCP
Who am I on MCTL?The mctl_whoami tool returns your identity and accessible namespaces. Use mctl_get_tenant(name="my-team") to inspect a specific tenant you can access. mctl_list_tenants lists all platform tenants and requires platform admin access. If you need to create one:
Create a new tenant called my-teamThis calls mctl_create_tenant and submits asynchronous provisioning. Human sign-in and access to platform UIs must also be configured. See Tenant Management for details.
Step 2: Prepare a repository
Your application must live in a GitHub repository that MCTL can access. Before continuing:
- The repository exists under a GitHub user or organisation account.
- The default branch (
main) contains the application source code. - No credentials or secrets are committed to the repository.
If you do not have a repository yet, create one on GitHub, push your source code, and return to this guide.
Step 3: Add a Dockerfile
By default MCTL builds your container image from a Dockerfile at the repository root. A minimal Node.js example is shown below. For other runtimes (Python, Go, static SPA) see Scaffolding. The image must run as a numeric non-root user and write only to /tmp; see Container requirements.
Monorepo or Dockerfile outside the repository root
If your Dockerfile lives somewhere other than the repository root (for example a monorepo with the app under apps/api/Dockerfile), pass dockerfile_path when you onboard the service in Step 6 — see the parameter table there. Omitting it defaults to Dockerfile at the repo root, which fails the build with open Dockerfile: no such file or directory if that is not where your Dockerfile actually is.
dockerfile
FROM node:22.11-alpine3.20
RUN apk add --no-cache tini
ENV NODE_ENV=production \
PORT=8787
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN addgroup -S -g 10001 app && adduser -S -u 10001 -G app app
USER 10001:10001
EXPOSE 8787
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD node -e "require('http').get('http://127.0.0.1:8787/healthz', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]Add unauthenticated GET /healthz and GET /readyz endpoints that return HTTP 200 when the application is alive and ready to serve traffic, respectively. The default chart uses both endpoints. If your app only exposes /healthz, explicitly pass health_check_path="/healthz" at onboarding to use it for both probes. Can't use /healthz? Pass health_check_path=/your-path when onboarding in Step 6 to override both liveness and readiness probe paths. MCTL uses this endpoint for liveness checks.
Commit the Dockerfile to the default branch before proceeding.
Step 4: Grant MCTL access to the repository
MCTL needs read access to your repository to build the container image. If your repository is already accessible (for example, you previously granted access for another service in the same organisation), you can skip this step and proceed directly to Step 5.
Via MCP
Ask your AI assistant:
Grant access to repo <owner>/<repo> for my-teamThis calls mctl_grant_repo_access(team="my-team", repo="<owner>/<repo>"). The tool returns a GitHub App installation URL. Open that URL in your browser and install the MCTL GitHub App on the repository or organisation.
TIP
The portal path for granting repository access is not yet documented. Use the MCP path above, then return to the portal for subsequent steps if you prefer the graphical interface. A follow-up will add portal instructions once the exact UI path is confirmed.
Step 5: Sync repositories
After installing the GitHub App, sync the repository list so MCTL registers the newly accessible repository.
Via MCP
Sync repos for my-teamThis calls mctl_sync_repos(team="my-team").
TIP
Repository sync via the portal is not yet documented. Use the MCP path above. A follow-up will add portal instructions once the exact UI path is confirmed.
Step 6: Onboard the service
The onboard step creates the GitOps manifests and submits the first Argo Workflow to build and deploy your container image.
Via MCP
Ask your AI assistant:
Onboard service my-service from repo <owner>/<repo> tag 0.1.0 port 8787 for my-teamThis calls:
mctl_deploy_service(
action="onboard",
team_name="my-team",
component_name="my-service",
dockerfile_repo="<owner>/<repo>",
git_tag="0.1.0",
port="8787",
service_template="default"
)If your Dockerfile is not at the repository root (a monorepo, for example), add dockerfile_path:
mctl_deploy_service(
action="onboard",
team_name="my-team",
component_name="my-service",
dockerfile_repo="<owner>/<repo>",
dockerfile_path="apps/api/Dockerfile",
git_tag="0.1.0",
port="8787",
service_template="default"
)Parameters:
| Parameter | Description |
|---|---|
action | Always "onboard" for a first-time deploy |
team_name | Your tenant name from Step 1 |
component_name | The service name — used in the generated URL |
dockerfile_repo | GitHub repository in <owner>/<repo> format |
git_tag | The Git tag to build. Tag your repository before onboarding (git tag 0.1.0 && git push --tags) |
port | The container port your application listens on (matches the EXPOSE in your Dockerfile) |
service_template | Use "default" unless you have a specific template |
dockerfile_path | Optional. Path to the Dockerfile relative to the repo root. Defaults to "Dockerfile" (repo root). Set this for monorepos, e.g. "apps/api/Dockerfile" |
health_check_path | Optional. Path used for both liveness and readiness probes. When omitted, liveness uses /healthz and readiness uses /readyz. Set this to use one endpoint for both, e.g. "/healthz" or "/api/health" |
The tool returns a workflow name. MCTL submits an Argo Workflow in the background — proceed to Step 7 to track it.
For a complete CI setup that auto-deploys on every push to main, see Scaffolding.
Step 7: Track the workflow status
The onboard operation runs as an Argo Workflow. Check its status to confirm the deployment succeeded before accessing the service URL.
Via MCP
What is the status of the deploy workflow for my-service?This calls mctl_get_workflow_status with the workflow name returned in Step 6. After workflow completion, check mctl_get_service_status for Synced/Healthy and verify the application endpoint.
If your operator has granted separate Argo Workflows UI access, you can also browse workflows at workflows.mctl.ai.
TIP
If the workflow fails, check the logs at workflows.mctl.ai or ask your AI assistant for the workflow logs. Common causes are a missing /healthz or /readyz endpoint, or a Dockerfile that fails to build.
Step 8: Open the generated service URL
Every service receives an auto-generated URL in the form:
https://<team>-<service>.mctl.aiFor example, if your team is my-team and your service is my-service, the URL is https://my-team-my-service.mctl.ai.
Verify the service is responding:
bash
curl https://my-team-my-service.mctl.ai/healthz(If you passed health_check_path in Step 6, curl that path instead — and update the Dockerfile's HEALTHCHECK line to match too. health_check_path only changes the Kubernetes liveness/readiness probes; it can't rewrite the image's own HEALTHCHECK, so an app exposing only the custom path will still fail Docker's own health check even though the platform reports it healthy.)
Expect HTTP 200; the response body depends on your application. If it fails, inspect service health, probe configuration, ingress and DNS rather than assuming workflow completion proves readiness.
Optional: Add a custom domain
To serve your application on your own domain (for example, api.example.com):
Via MCP
Add domain api.example.com to my-service in my-teamThis calls mctl_add_custom_domain. MCTL returns the CNAME target to configure in your DNS provider. TLS is provisioned automatically once the CNAME record propagates.
See Custom Domains for verification steps and troubleshooting.
Optional: Manage the platform through MCP
Once your service is running you can manage it entirely through your AI client. Common next steps:
- Deploy a new version — push a new Git tag and ask your assistant to deploy it, or set up the CI auto-deploy job from Scaffolding.
- Autoscale — configure HPA bounds and the CPU threshold; see Scaling.
- Roll back — ask your assistant to roll back
my-serviceto a previous tag. - View logs — ask your assistant to show recent logs for
my-service.
The full list of available tools is in the MCP Tools Reference. For a curated set of example prompts, see MCP Examples.