← Control PlaneCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Control Plane
Snapshot Sep 30, 2026 · 23:00 UTC · version 1.0.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"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.",
"included_files": [],
"skill_md_contents": "---\nname: gitops-cicd\ndescription: \"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.\"\n---\n\n# GitOps & CI/CD\n\nIn 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 — `get_resource_schema` before authoring manifests, `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.\n\n## Service-account authentication\n\n```bash\ncpln serviceaccount create --name ci-deployer --org ORG\ncpln serviceaccount add-key ci-deployer --description \"ci key\" --org ORG # --description is required\n```\n\nThe JSON response's `key` value is the credential — store it as a masked/secret variable in the CI platform. MCP: `add_key_to_service_account` does both steps (and creates the service account if missing).\n\nGrant 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 (`create_policy`).\n\nSet in the platform's variable settings, never inline in scripts:\n\n| Variable | Role |\n|---|---|\n| `CPLN_TOKEN` | Service-account key (secret/masked) |\n| `CPLN_ORG` | Target org |\n| `CPLN_GVC` | Target GVC, when the pipeline targets one |\n| `CPLN_SKIP_UPDATE_CHECK=1` | Silence CLI update checks in logs |\n\nWith `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.\n\n## Installing the CLI on runners\n\n- npm (runner has Node 16+): `npm install -g @controlplane/cli@X.Y.Z` — pin the version. This installs both `cpln` **and** `docker-credential-cpln`.\n- Slim or non-Node images: the binary tarball — copy **both** binaries onto PATH. The [containers guide](https://docs.controlplane.com/cli-reference/ci-cd-development/container-image.md) has Dockerfiles for each method, and covers running the CLI inside cron workloads.\n\n## Building images in CI: pick the flow by runner capability\n\n`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.\n\n| Runner | Build flow |\n|---|---|\n| 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` |\n| 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 |\n\n**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.\n\nThe 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:\n\n```bash\necho \"$CPLN_TOKEN\" | docker login ORG.registry.cpln.io -u '<token>' --password-stdin\n```\n\nWith 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.\n\nGitLab job without a daemon (kaniko; the runner must be amd64 — kaniko cannot cross-build):\n\n```yaml\nbuild:\n image:\n name: gcr.io/kaniko-project/executor:debug\n entrypoint: [\"\"]\n script:\n - mkdir -p /kaniko/.docker\n - printf '{\"auths\":{\"%s.registry.cpln.io\":{\"username\":\"<token>\",\"password\":\"%s\"}}}' \"$CPLN_ORG\" \"$CPLN_TOKEN\" > /kaniko/.docker/config.json\n - /kaniko/executor --context \"$CI_PROJECT_DIR\" --destination \"$CPLN_ORG.registry.cpln.io/my-app:$CI_COMMIT_SHORT_SHA\"\n```\n\nOn 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.\n\n**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.\n\n## Applying manifests\n\nAuthor YAML against the real shape first: `get_resource_schema` for each kind. The pipeline then runs:\n\n```bash\ncpln apply --file ./manifests/ --ready\n```\n\n- `--file` takes a file, a multi-document YAML (`---`), repeated `--file` flags, a directory (recursed; only `.yaml`/`.yml`/`.json` are picked up), or stdin (`--file -`).\n- 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.\n- Apply is an upsert. **Renaming a resource in git creates a new resource**; the old one survives until deleted explicitly.\n- A manifest with an inline `gvc:` that differs from `--gvc`/`CPLN_GVC` aborts the whole apply.\n- `--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.\n- Seed the repo from a live resource: `cpln workload get NAME -o yaml-slim > workload.yaml` (strips server-managed fields).\n- For Helm-chart-shaped releases, `cpln helm install|upgrade|rollback` tracks revisions — the platform's only rollback primitive (`environment-promotion` skill).\n\n## GitHub Actions example\n\n```yaml\nname: deploy\non: { push: { branches: [main] } }\nenv:\n CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }}\n CPLN_ORG: my-org\n CPLN_GVC: my-gvc\njobs:\n deploy:\n runs-on: ubuntu-latest # GitHub-hosted: Docker daemon available\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with: { node-version: 22 }\n - run: npm install -g @controlplane/cli@X.Y.Z\n - run: cpln image build --name my-app:${GITHUB_SHA:0:7} --push\n # manifests reference //image/my-app:IMAGE_TAG — substitute per commit\n - run: sed -i \"s|IMAGE_TAG|${GITHUB_SHA:0:7}|\" manifests/workload.yaml\n - run: cpln apply --file ./manifests/ --ready\n```\n\nOfficial starter repos (CLI): [GitHub Actions](https://github.com/controlplane-com/github-actions-example-cli), [GitLab CI](https://gitlab.com/controlplane-com/gitlab-pipeline-example-cli), [Bitbucket](https://bitbucket.org/controlplane-com/bitbucket-pipeline-example-cli), [CircleCI](https://github.com/controlplane-com/circle-ci-pipeline-example-cli), [Google Cloud Build](https://github.com/controlplane-com/google-cloud-build-example-cli). Terraform pipelines: `iac-terraform-pulumi` skill.\n\n## Verify\n\n- In-pipeline: the `cpln apply --ready` exit code is the deploy gate.\n- Out-of-band: `list_deployments` for per-location readiness, `get_resource` (kind=\"image\") to confirm the push landed. A workload that never goes ready: `workload` skill or the `/cpln:troubleshoot` command.\n\n## Troubleshooting\n\n| Symptom | Cause / fix |\n|---|---|\n| `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` |\n| 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 |\n| 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 |\n| 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 |\n| `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 |\n| `docker login` or push gets 401 | Username must be the literal `<token>`; the key is the password; check it wasn't truncated |\n| Push rejected for permissions | Pipeline service account lacks `create` on the `image` kind (`access-control`) |\n| `cpln apply` 403 on one kind | Service-account policy doesn't cover that kind — grant per-kind create/edit |\n| Apply aborts: `--gvc option ... does not match the gvc value` | Inline `gvc:` in a manifest disagrees with `--gvc`/`CPLN_GVC` |\n| Pipeline pushed, workload kept the old code | Same tag re-pushed — use unique tags, `supportDynamicTags`, or `cpln workload force-redeployment` |\n| `--ready` exits non-zero after ~5 min | Workload never became ready — check `list_deployments` and workload events |\n| `exec format error` at runtime | Image isn't `linux/amd64` (`image` skill) |\n\n## Quick reference\n\n| Tool | Purpose |\n|---|---|\n| `get_resource_schema` | Manifest shape for any kind before authoring |\n| `add_key_to_service_account` | Pipeline service account + key in one call |\n| `create_policy` | Scope the pipeline service account's permissions |\n| `list_deployments` | Per-location readiness after a deploy |\n| `export_terraform` / `convert_to_terraform` | Seed IaC pipelines from live resources or manifests |\n\n## Related skills\n\n| Skill | When |\n|---|---|\n| `cpln` | CLI conventions — profile-less sessions, flag/env/profile precedence |\n| `image` | Build mechanics — the remote vs local table, buildpacks, registry auth, pull secrets |\n| `environment-promotion` | Moving images/configs across dev/staging/prod, rollback patterns |\n| `iac-terraform-pulumi` | Terraform/Pulumi pipelines instead of `cpln apply` |\n| `access-control` | Service accounts, groups, policies, least privilege |\n\n## Documentation\n\n- [CI/CD usage](https://docs.controlplane.com/cli-reference/ci-cd-development/ci-cd.md)\n- [Using the CLI in containers](https://docs.controlplane.com/cli-reference/ci-cd-development/container-image.md)\n- [CI/CD example repos](https://docs.controlplane.com/guides/gitops.md)\n- [cpln apply](https://docs.controlplane.com/guides/cpln-apply.md)\n- [Create a service account](https://docs.controlplane.com/guides/create-service-account.md)\n"
}SHA-256: 14f1a4105f687486257e2299454c0ca601645880aed6c44e7c9ba99490fe99bc