| name | gitops-cicd |
| description | Sets up CI/CD pipelines and GitOps for Control Plane. Use when the user asks about GitHub Actions, GitLab CI, Bitbucket, CircleCI, building images in CI, kaniko, cpln apply in pipelines, or service-account tokens for CI. |
GitOps & CI/CD
Tool availability: some MCP tools named here live in the full toolset profile — if one is not advertised on this connection, tell the user to reconnect the MCP server with ?toolsets=full (or use the cpln CLI fallback). Reads and deletes work on every profile via the generic list_resources / get_resource / delete_resource tools.
In pipelines the CLI is the primary interface: authenticate with a service-account key in CPLN_TOKEN (no profile needed), push an image, cpln apply --ready the manifests. MCP tools do the work around the pipeline — mcp__cpln__get_resource_schema before authoring manifests, mcp__cpln__list_deployments to confirm a deploy landed. The usual failure is image builds: cpln image build runs the build locally through Docker, so a runner without a daemon needs a different flow — a daemonless builder, or --remote to build on Control Plane. Pick by runner capability, not by habit.
Service-account authentication
cpln serviceaccount create --name ci-deployer --org ORG
cpln serviceaccount add-key ci-deployer --description "ci key" --org ORG
The JSON response's key value is the credential — store it as a masked/secret variable in the CI platform. MCP: mcp__cpln__add_key_to_service_account does both steps (and creates the service account if missing).
Grant least privilege (access-control skill): pushing images needs create on the image kind; cpln apply needs create/edit on every kind the manifests contain. cpln group add-member superusers --serviceaccount ci-deployer works but grants full org access — prefer a scoped policy (mcp__cpln__create_policy).
Set in the platform's variable settings, never inline in scripts:
| Variable | Role |
|---|
CPLN_TOKEN | Service-account key (secret/masked) |
CPLN_ORG | Target org |
CPLN_GVC | Target GVC, when the pipeline targets one |
CPLN_SKIP_UPDATE_CHECK=1 | Silence CLI update checks in logs |
With CPLN_TOKEN set the CLI runs a profile-less session; resolution is flag, then env var, then profile (cpln skill). The official example repos persist the token instead — cpln profile update default --token "$CPLN_TOKEN" (create is an alias of update) — either works. Never pass --token on ad-hoc commands and never echo the token.
Installing the CLI on runners
- npm (runner has Node 16+):
npm install -g @controlplane/cli@X.Y.Z — pin the version. This installs both cpln and docker-credential-cpln.
- Slim or non-Node images: the binary tarball — copy both binaries onto PATH. The containers guide has Dockerfiles for each method, and covers running the CLI inside cron workloads.
Building images in CI: pick the flow by runner capability
cpln image build --push builds locally: with a Dockerfile it shells out to docker buildx build, otherwise it downloads the pack CLI and runs buildpacks. It needs a working Docker daemon and docker-credential-cpln on PATH, and it configures registry auth itself — no separate docker-login step. Flag behavior and upload rules: image skill.
| Runner | Build flow |
|---|
Daemon available — GitHub-hosted runners, GitLab with the docker:dind service (privileged runners, including gitlab.com SaaS), CircleCI setup_remote_docker, Bitbucket docker service | cpln image build --name APP:TAG --push, or keep an existing docker-native pipeline: login below, then docker build --platform linux/amd64 + docker push |
| No daemon — self-managed GitLab runners without privileged mode, locked-down Kubernetes executors | A daemonless builder (kaniko, buildah, rootless BuildKit) pushing straight to the registry, or cpln image build --name APP:TAG --remote, which builds on Control Plane with only CPLN_TOKEN and the org |
A remote build gives up the local build options. --dockerfile, --builder, --buildpack, --env, --env-file, and --platform do not apply — the service detects the build itself and always produces linux/amd64. Choose a daemonless builder instead when a job needs build args or a non-amd64 target. --repo https://github.com/... --branch main skips the checkout and builds what the service clones; a private repo needs the org's git connection, and in a non-interactive pipeline the CLI prints an authorization URL and exits, so authorize it once from a workstation first.
The org registry is a standard Docker registry: ORG.registry.cpln.io, username = the literal string <token>, password = the service-account key. Any tool that can push an OCI image works:
echo "$CPLN_TOKEN" | docker login ORG.registry.cpln.io -u '<token>' --password-stdin
With the CLI installed, cpln image docker-login is the faster equivalent for raw docker push/docker pull jobs: instead of storing a secret it registers the docker-credential-cpln helper for the org registry, and Docker resolves the token from CPLN_TOKEN (or the profile) at every later call. Use the raw docker login form only where the CLI isn't on the box — kaniko auth files, CLI-less build jobs.
GitLab job without a daemon (kaniko; the runner must be amd64 — kaniko cannot cross-build):
build:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- printf '{"auths":{"%s.registry.cpln.io":{"username":"<token>","password":"%s"}}}' "$CPLN_ORG" "$CPLN_TOKEN" > /kaniko/.docker/config.json
- /kaniko/executor --context "$CI_PROJECT_DIR" --destination "$CPLN_ORG.registry.cpln.io/my-app:$CI_COMMIT_SHORT_SHA"
On GitHub, docker/login-action + docker/build-push-action also work with the same registry/credentials. Images must be linux/amd64; buildpack and multi-platform detail in the image skill.
Tag every build uniquely ($CI_COMMIT_SHORT_SHA, ${GITHUB_SHA:0:7}). Re-pushing the same tag does not redeploy workloads — if a tag must be reused, set supportDynamicTags on the workload or run cpln workload force-redeployment WORKLOAD after the push.
Applying manifests
Author YAML against the real shape first: mcp__cpln__get_resource_schema for each kind. The pipeline then runs:
cpln apply --file ./manifests/ --ready
--file takes a file, a multi-document YAML (---), repeated --file flags, a directory (recursed; only .yaml/.yml/.json are picked up), or stdin (--file -).
- One invocation sorts everything by kind — agent, secret, cloudaccount, gvc, identity, volumeset, policy, workload, then all remaining kinds — so a workload and its GVC can live in one file in any order.
cpln delete --file applies the reverse order.
- Apply is an upsert. Renaming a resource in git creates a new resource; the old one survives until deleted explicitly.
- A manifest with an inline
gvc: that differs from --gvc/CPLN_GVC aborts the whole apply.
--ready waits only for the workloads applied in that run: 5-second polls, three consecutive ready checks to pass, a ~5-minute cap, non-zero exit on timeout — a usable deploy gate.
- Seed the repo from a live resource:
cpln workload get NAME -o yaml-slim > workload.yaml (strips server-managed fields).
- For Helm-chart-shaped releases,
cpln helm install|upgrade|rollback tracks revisions — the platform's only rollback primitive (environment-promotion skill).
GitHub Actions example
name: deploy
on: { push: { branches: [main] } }
env:
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }}
CPLN_ORG: my-org
CPLN_GVC: my-gvc
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm install -g @controlplane/cli@X.Y.Z
- run: cpln image build --name my-app:${GITHUB_SHA:0:7} --push
- run: sed -i "s|IMAGE_TAG|${GITHUB_SHA:0:7}|" manifests/workload.yaml
- run: cpln apply --file ./manifests/ --ready
Official starter repos (CLI): GitHub Actions, GitLab CI, Bitbucket, CircleCI, Google Cloud Build. Terraform pipelines: iac-terraform-pulumi skill.
Verify
- In-pipeline: the
cpln apply --ready exit code is the deploy gate.
- Out-of-band:
mcp__cpln__list_deployments for per-location readiness, mcp__cpln__get_resource (kind="image") to confirm the push landed. A workload that never goes ready: workload skill or the /cpln:troubleshoot command.
Troubleshooting
| Symptom | Cause / fix |
|---|
Cannot connect to the Docker daemon from cpln image build | Runner has no daemon — enable dind/privileged mode, switch to a daemonless builder, or build remotely with --remote |
A flag is rejected together with --remote | Expected — the service picks the method and always pushes linux/amd64, so the local build options do not apply. A job that needs build args or another arch wants a daemonless builder |
| A remote build prints an authorization URL and the job exits | The org has no git connection for that private repo yet — authorize once interactively, then re-run the pipeline |
| Remote build upload rejected — over 500 MB / 20,000 files | Add large paths to .dockerignore (or .gitignore) — a remote folder build uploads the whole context |
The docker-credential-cpln command is not accessible on --push | Helper missing from PATH — npm installs it next to cpln; binary installs must copy both binaries |
docker login or push gets 401 | Username must be the literal <token>; the key is the password; check it wasn't truncated |
| Push rejected for permissions | Pipeline service account lacks create on the image kind (access-control) |
cpln apply 403 on one kind | Service-account policy doesn't cover that kind — grant per-kind create/edit |
Apply aborts: --gvc option ... does not match the gvc value | Inline gvc: in a manifest disagrees with --gvc/CPLN_GVC |
| Pipeline pushed, workload kept the old code | Same tag re-pushed — use unique tags, supportDynamicTags, or cpln workload force-redeployment |
--ready exits non-zero after ~5 min | Workload never became ready — check mcp__cpln__list_deployments and workload events |
exec format error at runtime | Image isn't linux/amd64 (image skill) |
Quick reference
| Tool | Purpose |
|---|
mcp__cpln__get_resource_schema | Manifest shape for any kind before authoring |
mcp__cpln__add_key_to_service_account | Pipeline service account + key in one call |
mcp__cpln__create_policy | Scope the pipeline service account's permissions |
mcp__cpln__list_deployments | Per-location readiness after a deploy |
mcp__cpln__export_terraform / mcp__cpln__convert_to_terraform | Seed IaC pipelines from live resources or manifests |
Related skills
| Skill | When |
|---|
cpln | CLI conventions — profile-less sessions, flag/env/profile precedence |
image | Build mechanics — the remote vs local table, buildpacks, registry auth, pull secrets |
environment-promotion | Moving images/configs across dev/staging/prod, rollback patterns |
iac-terraform-pulumi | Terraform/Pulumi pipelines instead of cpln apply |
access-control | Service accounts, groups, policies, least privilege |
Documentation