← 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
{
"description": "Promotes workloads across dev/staging/production on Control Plane. Use when the user asks about environment promotion, org-per-environment, cross-org image pulls, image promotion, deploying to production, or rollback.",
"included_files": [],
"name": "environment-promotion",
"skill_md_contents": "---\nname: environment-promotion\ndescription: \"Promotes workloads across dev/staging/production on Control Plane. Use when the user asks about environment promotion, org-per-environment, cross-org image pulls, image promotion, deploying to production, or rollback.\"\n---\n\n# Environment Promotion\n\nControl Plane has **no built-in promote or rollback primitive** — promotion is applying the same artifacts (image + manifests) to the next environment. Two topologies exist: **org-per-environment (the documented best practice)** and GVC-per-environment. The recurring failure is image access: a staging/prod org cannot pull the dev org's images until you either copy the image or wire up a cross-org pull secret.\n\n## Choosing a topology\n\n| Topology | Isolation | Image sharing | Best for |\n|---|---|---|---|\n| **Org per environment** (recommended) | Strongest — policies, secrets, users, audit fully separate | `cpln image copy` or cross-org pull secret | Production, compliance-sensitive teams |\n| **GVC per environment** (one org) | Weak — shared org policies and access | Same org registry; no pull secret needed | Small teams, rapid iteration |\n\nWith org-per-environment, GVCs and workloads keep **identical names** in every org, so the same manifests apply unchanged — no environment suffixes in resource names. Org creation is account-level: Console, or `cpln org create --accountId ID --invitee EMAIL`.\n\n## Promoting manifests\n\nKeep manifests in git and apply them per environment — `cpln apply` is idempotent (PUT upsert) and resolves resource ordering:\n\n```bash\ncpln apply --file ./manifests/ --org my-org-staging --gvc my-gvc --ready # org-per-env: same files, next org\ncpln apply --file ./manifests/ --org my-org --gvc staging-gvc --ready # gvc-per-env: same files, next GVC\n```\n\n- Bootstrap manifests from a live environment with `cpln <resource> get REF -o yaml-slim` (plain `yaml` output breaks apply).\n- Environment differences (env vars, scaling, firewall) belong in the manifests per environment — or patch after apply with `update_workload` / `update_gvc` (both PATCH semantics).\n- For IaC-based promotion, export live resources to Terraform: `export_terraform` (one self link, or bulk by path depth — a whole GVC or org), `export_terraform_batch` (full profile, up to 100 explicit links), `convert_to_terraform` (manifest to HCL, dry-run validated). An unsupported kind is rejected with the supported list.\n\n## Sharing images across orgs\n\n### Option A — copy the image (one-time promotions)\n\n```bash\ncpln image copy my-app:abc1234 --to-org my-org-prod # same credentials for both orgs\ncpln image copy my-app:abc1234 --to-org my-org-prod --to-profile prod-profile --cleanup\n```\n\nCLI-only (no MCP tool) and **still requires a running Docker daemon — `copy` has no `--remote` mode** — it docker-logins both registries, then pulls, tags, pushes. `--to-name` renames during copy; `--cleanup` removes the local images (use in CI). Needs `pull` permission on the source image and `create` on images in the target org. After the copy the target references it as `//image/my-app:abc1234` — no pull secret.\n\n### Option B — cross-org pull secret (continuous access)\n\nThe target org pulls directly from the source org's registry. Four steps:\n\n1. **Source org — puller credentials**: `add_key_to_service_account` (creates the service account if missing; the key is shown **once**).\n2. **Source org — grant pull**: `create_policy` with `targetKind: image`, `targetAll: true` (or `targetQuery` by repository), `addPermissions: [\"pull\"]`, `addServiceAccounts: [LINK]` — bindings go in the create call.\n3. **Target org — docker secret**: have the user create a `docker` secret with this `dockerConfigJson` — offer a manifest scaffold (`data` is this JSON as one string; `setup-secret` skill); the username is the **literal string `<token>`** (the registry rejects anything else; the password is the service-account key):\n\n```json\n{ \"auths\": { \"my-org-dev.registry.cpln.io\": { \"username\": \"<token>\", \"password\": \"SERVICE_ACCOUNT_KEY\" } } }\n```\n\n4. **Target org — attach to the GVC**: `update_gvc` with `pullSecretLinks: [\"//secret/dev-registry-pull\"]` (merged with existing), then reference the image by its **full registry hostname** in the workload spec:\n\n```yaml\nspec:\n containers:\n - name: main\n image: my-org-dev.registry.cpln.io/my-app:abc1234\n```\n\nCLI fallback for the same four steps:\n\n```bash\ncpln serviceaccount create --name image-puller --org my-org-dev # CLI does NOT auto-create on add-key\ncpln serviceaccount add-key image-puller --description \"cross-org pull\" --org my-org-dev # save the key\ncpln policy create --name image-pull --target-kind image --all --org my-org-dev\ncpln policy add-binding image-pull --serviceaccount image-puller --permission pull --org my-org-dev\n# the user creates the dev-registry-pull docker secret in my-org-prod, then:\ncpln gvc update my-gvc --set 'spec.pullSecretLinks+=//secret/dev-registry-pull' --org my-org-prod\n```\n\nSame-org images never need a pull secret — the platform injects a default registry credential for the org's own registry automatically.\n\n## Image tags across environments\n\n- **Promote immutable tags** (git SHA `my-app:abc1234` or semver `my-app:v1.2.3`) — promote the exact artifact you tested; mutable tags (`latest`, `staging`) make rollback unreliable. Digest pins (`my-app@sha256:...`) are maximally reproducible.\n- **`supportDynamicTags`** (workload spec, default `false`): redeploys the workload automatically when a tag's underlying digest changes (within ~5 minutes) — useful for dev environments on mutable tags, wrong for production promotion.\n\n## CI/CD promotion pipeline\n\nThe CLI is the primary interface in pipelines; `CPLN_TOKEN` alone is enough (no profile needed — see the `cpln` skill). The shape that works:\n\n```yaml\n# Build once in dev, then per stage: copy the image + apply the manifests\n- run: cpln image build --name my-app:${{ github.sha }} --push # CPLN_TOKEN + CPLN_ORG=my-org-dev\n- run: cpln apply --file ./manifests/ --gvc my-gvc --ready\n\n# staging / prod stages (gate each with environment approvals):\n- run: cpln image copy my-app:${{ github.sha }} --to-org my-org-prod --cleanup # dev-org token\n- run: cpln apply --file ./manifests/ --gvc my-gvc --org my-org-prod --ready # prod-org token\n```\n\n- **One service-account token per org** — a dev-org token must not be able to touch prod; the copy step runs with source-org credentials plus a `--to-profile` (or pre-run `cpln image docker-login`) for the target.\n- **`--ready` gates promotion** — it polls until workloads are healthy (5s interval, up to 5 min) and fails the job otherwise.\n- Approval gates (GitHub environments, GitLab manual jobs) go between stages. Full pipeline setup, npm install (`@controlplane/cli`), runners: `gitops-cicd` skill.\n\n## Rollback\n\nThere is no deployment-history rollback — rolling back means **re-pointing the workload at the previous known-good image** (keep the previous tag in git history or your pipeline metadata):\n\n```bash\ncpln workload update my-app --set spec.containers.main.image=//image/my-app:v1.1.0 --gvc my-gvc --org my-org\ncpln workload get-deployments my-app --gvc my-gvc --org my-org # verify every location reports ready\n```\n\n- MCP path: `get_resource` (kind=\"workload\") to record the current image, `update_workload` (`containers: [{name, image}]` — merged by container name), then poll `list_deployments` until ready.\n- Org-per-environment: confirm the older image still exists in **this** org's registry first (`list_resources` kind=\"image\") — it may only have been copied forward once.\n- **Restart without changing the image**: `cpln workload force-redeployment my-app --gvc GVC` — it PATCHes a `cpln/deployTimestamp` tag with the current time, producing a rolling restart. No MCP equivalent; `update_workload` setting that same tag replicates it.\n- Helm-managed releases are the exception with real revision history: `cpln helm rollback RELEASE [REVISION]`.\n\n## Quick reference — MCP tools\n\n| Tool | Purpose |\n|---|---|\n| `create_gvc` / `create_workload` | Stand up the target environment |\n| `update_workload` / `update_gvc` | Patch image, env, scaling, `pullSecretLinks` (PATCH semantics) |\n| `add_key_to_service_account` | Puller credentials in the source org (auto-creates the SA; key shown once) |\n| `create_policy` | Grant `pull` on images, binding included in the create call |\n| `get_resource` (kind `secret`) | Verify the docker pull secret exists before attaching |\n| `list_deployments` | Verify a promotion or rollback is ready per location |\n| `export_terraform` / `_batch` / `convert_to_terraform` | Export live environments to IaC |\n\n**CLI fallback** (read the `cpln` skill first; CI/CD uses `CPLN_TOKEN` + `cpln apply --ready`): `cpln image copy` is CLI-only and needs a local Docker daemon. `cpln image build --remote` needs none; over MCP only a **repo** build can be started — a local folder must go through the CLI (`image` skill).\n\n## Related skills\n\n| Need | Skill |\n|---|---|\n| Image building, registries, pull-secret detail | `image` |\n| Pipeline setup, runners, service-account auth | `gitops-cicd` |\n| Terraform / Pulumi promotion | `iac-terraform-pulumi` |\n| Per-environment secrets and RBAC | `access-control` |\n\n## Documentation\n\n- [Environment Promotion Guide](https://docs.controlplane.com/guides/environment-promotion.md)\n- [Copy an Image Guide](https://docs.controlplane.com/guides/copy-image.md)\n- [cpln apply Guide](https://docs.controlplane.com/guides/cpln-apply.md)\n"
}SHA-256 of public snapshot: 66d9ebd9994c984f34da37de4c5040335d4f4a0395db0859b0a30e084e073e78