UNOFFICIAL/EXPERIMENTAL. This walkthrough answers one question: I have a simple app; how do I store it as a base, make staging and production variants, promote a change, and deliver each environment through GitOps?
The variant and promotion sequence was run live on 2026-07-02. The cluster and OCI commands have since moved into the main cub CLI. The commands below use the current cub cluster and cub release publish path rather than preserving the retired command spelling from that run.
The chain:
New to cub? Install the cub CLI first. You can pull and render public catalog packages without an account. Commands that save or change ConfigHub data require you to sign in.
your app (plain manifests)
-> cub variant upload -> base Space
-> cub variant create (x2) -> staging and production Spaces
-> cub release publish -> one OCI release per environment
-> Argo CD pulls -> the reviewed objects run on the cluster
-> edit base, promote -> staged rollout, environment by environment
Prerequisites
cubinstalled and logged in (cub auth login).- Docker running (for the local kind cluster).
Step 1: Create a temporary cluster and its delivery target
cub cluster up --name demo --space demo-cluster
This provisions a kind cluster, installs Argo CD, creates a demo-cluster Space with an OCI target (demo-cluster/oci), installs the OCI pull credentials, and bootstraps a root Application. Load the cluster credentials in any shell that needs kubectl:
source ~/.confighub/clusters/demo.env
Step 2: A simple app, three manifests
A Deployment, ConfigMap, and Service, all in one namespace. Any plain manifests work; nothing here is chart-specific. For a committed four-object starting fixture and a focused upload receipt, use the plain YAML example. This longer walkthrough continues past import into variants, releases, GitOps delivery, and promotion.
Step 3: Upload the base
cub variant upload --component acme-web --variant base \
--space acme-web-base --granularity per-resource ./app
One Unit per resource lands in acme-web-base, links inferred from references. The upload warns about anything your manifests reference but do not contain (for example the namespace, or a Secret it expects to exist): read those warnings, they are the honest list of what the target cluster must provide.
Step 4: Staging and production variants
cub variant create staging acme-web-base \
--space-pattern "template:acme-web-staging" \
--environment Staging --namespace acme-staging \
--target demo-cluster/oci
cub variant create production acme-web-base \
--space-pattern "template:acme-web-prod" \
--environment Prod --namespace acme-prod \
--target demo-cluster/oci \
--unit-delete-gate customer-demo --unit-destroy-gate customer-demo
Each clone records its upstream link (promotion follows it later), rewrites the namespace on every cloned Unit (verified: the staging ConfigMap says namespace: acme-staging, prod says acme-prod), and production's Units are gated against accidental delete and destroy. Pass --space-pattern for a predictable Space name; the server default produces a longer derived slug.
Step 5: Publish one release OCI for each environment
cub release publish acme-web-staging
cub release publish acme-web-prod
Each command packages the exact Units in that Space. The pull URLs are oci://oci.hub.confighub.com:443/space/acme-web-staging and oci://oci.hub.confighub.com:443/space/acme-web-prod.
Step 6: Tell Argo about the two environments, as config
The Argo Application objects are themselves Units in the cluster Space, so even the delivery wiring is versioned config:
cub unit create --space demo-cluster acme-web-staging-app app-staging.yaml \
--target demo-cluster/oci --change-desc "Argo Application for the staging variant"
The Application source is the Space release OCI:
source:
repoURL: oci://oci.hub.confighub.com:443/space/acme-web-staging
targetRevision: latest
path: .
Same again for prod (path ./acme-web-prod, destination namespace acme-prod).
Step 7: Publish the cluster Space, and watch it become real
cub release publish demo-cluster
The root Application created by cub cluster up reads the cluster Space release and creates the two child Applications. Each child pulls its environment's release OCI. In the recorded run, both apps reached Synced/Healthy and both namespaces served VERSION 1.
Step 8: The staged rollout
Change the base with a function (a recorded revision with your reason), then promote one environment at a time:
cub run set-yq --space acme-web-base --unit acme-configmap-acme-web-content \
--change-desc "Release VERSION 2 of the content" \
'.data."index.html" = "acme-web VERSION 2\n"'
cub variant promote acme-web-staging --dry-run
cub variant promote acme-web-staging --change-desc "Promote VERSION 2 to staging"
cub release publish acme-web-staging
Observed live state after that, and this is the point of the whole model:
staging: acme-web VERSION 2
prod: acme-web VERSION 1
Two environments, one reviewed base, the difference explicit and deliberate. Then the same two commands for acme-web-prod, and both environments read VERSION 2. The prod Unit's revision history tells the story in three lines: CloneUnit (the variant was created), Invoke (the namespace rewrite), UpgradeUnit (the promotion).
Honest notes from the live run
- Use
cub run set-yq(or another mutating function) to edit data. Its read-only siblingyqaccepts--change-descand exits cleanly while changing nothing; if a promote then says nothing needs upgrading, check the base actually gained a revision (cub revision list). cub variant promote --dry-runprints a correct would-upgrade summary.--dry-run -o mutationscurrently prints nothing; do not read its silence as "nothing to promote".- Right after content lands, Argo can briefly report
OutOfSyncwith the content already correct; it settles on the next reconcile. - The cluster's kubeconfig is its own file (
~/.confighub/clusters/<name>.kubeconfig); source the generated env file rather than assuming your default kubeconfig has the context.
Teardown
cub cluster down --name demo
cub space delete acme-web-staging --recursive
cub space delete acme-web-prod --recursive-force # gates block plain delete, by design
cub space delete acme-web-base --recursive
Where this fits
- The variant model and the decision rule behind it: variants after upload.
- Delivery shapes beyond Argo, including Flux and the no-controller path: cub deployment path.
- The catalog's receipted evidence for the same mechanism per chart base variant: the master matrix Promotion lane.