1. What changes, and what does not
Flux stays the delivery controller. Preview has no account or cluster mutation; import makes a parallel ConfigHub copy while Git still delivers; handover then moves sources to reviewed releases. Handover has checks and recovery steps, not zero risk.
Start with a small disposable estate. Roots, generators, and pruning can make production migration an ordered fleet operation.
Who does what, before and after
| Before the handover | After the handover | |
|---|---|---|
| Source of truth | Git | ConfigHub |
| Flux | reconciles each layer from Git | still reconciles each layer, from the release ConfigHub published |
| A change | is a commit to the Git path | is an edit in ConfigHub, released after the approvals its stage asks for |
| A commit to the old Git path | reaches the cluster | no longer reaches the cluster |
The handover is the step that moves the source of truth. Until handover.sh runs, ConfigHub holds a copy that nothing reads. The flux-system bootstrap and the Flux controllers stay outside ConfigHub, and the plan lists them.
Some teams on Argo CD? Bring your Argo CD apps into ConfigHub follows the same steps.
2. Preview the setup
Install the cub CLI and this released plugin:
cub plugin install confighub/examples@cub-flux-v0.5.1 --name flux
What you need: a local checkout of the Git repository this Flux installation reads, the cub CLI, and this plugin. Start in that repository's root; replace the directory placeholder below with its path. Planning reads the repository and needs no ConfigHub account or cluster.
cd <your-flux-gitops-repository>
cub flux plan . --require Healthy
--require Healthy makes later promotions wait for live health. Layers with workloads need wait or healthChecks; otherwise the reporter records health as Unknown. Planning itself does not prove health.
Read the result. The summary gives the estate or fleet size and the component and variant counts. The following lines map shared configuration to bases and cluster differences to variants. Check the stage order and warnings before continuing. This plan does not validate the destination.
Illustrative plan output
This is actual output from the public beginner fixture, not from your repository.
Flux fleet: 2 clusters, 2 layers, 4 variants
Read 16 objects
Clusters, one stage each, in order: dev (clusters/dev), prod (clusters/prod)
Reconcile order (dependsOn): infrastructure -> apps
infrastructure
base flux-infrastructure-base from gitops/flux/beginner/infrastructure/base
stage dev
dev variant flux-infrastructure-dev -> Target flux-targets/dev
Kustomization flux-system/infrastructure, path gitops/flux/beginner/infrastructure/dev
stage prod
prod variant flux-infrastructure-prod -> Target flux-targets/prod
Kustomization flux-system/infrastructure, path gitops/flux/beginner/infrastructure/prod
apps (after infrastructure)
base flux-apps-base from gitops/flux/beginner/apps/base
stage dev
dev variant flux-apps-dev -> Target flux-targets/dev
Kustomization flux-system/apps, path gitops/flux/beginner/apps/dev
adds namespace.yaml
namespace apptique-dev
label environment=dev
Deployment/frontend replace /spec/replicas = 1
Flux spec.healthChecks[0].namespace = apptique-dev
Flux spec.targetNamespace = apptique-dev
stage prod
prod variant flux-apps-prod -> Target flux-targets/prod
Kustomization flux-system/apps, path gitops/flux/beginner/apps/prod
adds namespace.yaml
namespace apptique-prod
label environment=prod
Deployment/frontend replace /spec/replicas = 3
Deployment/frontend replace /spec/template/spec/containers/0/resources/requests/cpu = 200m
Deployment/frontend replace /spec/template/spec/containers/0/resources/requests/memory = 128Mi
Deployment/frontend replace /spec/template/spec/containers/0/resources/limits/cpu = 400m
Deployment/frontend replace /spec/template/spec/containers/0/resources/limits/memory = 256Mi
Flux spec.healthChecks[0].namespace = apptique-prod
Flux spec.targetNamespace = apptique-prod
Sources
- GitRepository apptique-examples https://github.com/confighub/examples (gitops/flux/beginner/infrastructure/base/sources/apptique-examples.yaml)
branch: dev main, prod main
Bootstrap (stays outside ConfigHub)
- gitops/flux/beginner/clusters/dev/flux-system
- gitops/flux/beginner/clusters/prod/flux-system
Handover (apply will write it as handover.sh; plan runs nothing)
1. keep each layer's Flux Kustomization under its own name and switch its sourceRef to an OCIRepository on the ConfigHub gateway, so Flux keeps its inventory and nothing is reinstalled
2. first prove each variant renders exactly what Git renders today: infrastructure, apps prune, so anything the release lacks is deleted
3. leave flux-system alone: flux bootstrap owns it, like the Sveltos management record
Next
cub flux apply . --require Healthy --out ./flux-onboarding
That writes apply.sh, handover.sh and cleanup.sh. It runs nothing.
Read the full Flux planning reference for layouts and limits.
Try it on the fixture first
These commands print the output above. They need no repository of your own.
git clone https://github.com/confighub/examples.git
cd examples
git checkout 7f1b8f2fc849bb6488bc2c58f1469172018fc9dd
cd gitops/flux/beginner
cub flux plan . --require Healthy
Run the plan inside a Git checkout. In a copy with no .git directory the plan reports source paths as missing, and --repo-root <checkout> tells it where the repository starts.
What one Kustomization becomes
cub flux apply . --require Healthy --out onboard writes files only. For each cluster and layer it writes what Flux would run after the handover under onboard/layers/. On the fixture, onboard/layers/dev/apps.yaml holds the apps Kustomization from clusters/dev/apps.yaml with two fields changed, and one new OCIRepository beside it.
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
- path: ./gitops/flux/beginner/apps/dev
+ path: ./
sourceRef:
- kind: GitRepository
- name: apptique-examples
+ kind: OCIRepository
+ name: apps
The Kustomization keeps its name, so Flux keeps its record of what it applied. The new OCIRepository reads the Space the plan names as the variant, flux-apps-dev. The generated file holds placeholders for the gateway address, and the scripts fill them in.
A HelmRelease is stored as it is, and helm-controller goes on resolving it. The plugin does not render Helm charts. The fixture has no HelmRelease, so this page shows none.
How to go back
| After this step | Go back by |
|---|---|
cub flux plan . --require Healthy | Nothing to undo. |
cub flux apply . --require Healthy --out onboard | Delete onboard/. |
bash onboard/apply.sh | Run bash onboard/cleanup.sh. It removes what the script made in ConfigHub. Flux still reads Git. |
bash onboard/handover.sh | Stop the root pruning, restore both fields on each layer as the script recorded them, then remove the root. The script prints the commands in that order. Do not delete the ConfigHub Spaces first. |
You can stop here. The plan is read-only. To generate local scripts, connect delivery, or make a reviewed change, open the advanced steps below.
After the preview: save, connect delivery and make a change
3. Save the reviewed setup in ConfigHub
When the plan is clear, generate the setup directory. This writes files and scripts only.
cub flux apply . --require Healthy --out onboard
Read onboard/; delete it to discard this step. Flux keeps reading Git while you review the generated setup.
Check what a source will store
apply.sh stores what each Kustomization's source path renders to. Before you run it, you can check a source with cub config check from the Workshop plugin. It reads files only. On the fixture, the dev cluster's apps layer reads apps/dev. Check that path, not the files under onboard/layers/, which hold placeholders the scripts fill in:
cub plugin install confighub/cub-workshop@v0.6.61
kustomize build apps/dev > apps-dev.yaml
cub config check apps-dev.yaml
On the fixture it reports 4 objects (a Deployment, a Namespace, a Service and a ServiceAccount), no lifecycle work, and a passing schema check. The schema step names any field the API server would reject, so you can fix it in Git before you save.
Only the next command saves ConfigHub data. It needs cub auth login, kustomize, and any requested gateway. It creates bases, variants, stages, and releases, not cluster changes.
CONFIGHUB_OCI=<gateway> bash onboard/apply.sh
Keep this import separate from production handover; practice first with a disposable estate.
4. Connect deployment
This first cluster-changing step checks the reviewed release against the controller record before moving a source. Replace every angle-bracket value below.
The handover installs one small bootstrap root, then moves the layers it manages. Use the separate join script only for a new Flux cluster that has none of those layers already. Do not assume production can move one application at a time.
Review ownership first. Pruning can delete an object absent from the reviewed release. Confirm the printed way back, ownership, backups, and objects. Keep SOPS-encrypted workload Secrets and registry credentials outside review output. The generated bootstrap pull Secret is delivery access, not an application Secret.
Contexts, gateway, and fleet handover parameters
A gateway is the OCI address controllers use to pull ConfigHub releases: ConfigHub Cloud uses oci.hub.confighub.com, without a scheme. A context is the named kubeconfig context that reaches the stated cluster.
Flux runs handover one existing cluster at a time:
CONFIGHUB_OCI=oci.hub.confighub.com CLUSTER=<cluster> FLUX_CONTEXT=<kubectl-context> bash onboard/handover.sh
Advanced handover and recovery reference. Do not delete ConfigHub spaces as rollback: restore controller sources first, then use the script’s printed order.
5. Change, approve, and release
After handover, a reviewed ConfigHub change moves through stages. Independent approval is not default; configure it and separate credentials when needed.
A small change, approval, and first release
For Flux, make the change in ConfigHub after the layer has moved, then promote it through the stages the generated workflow created. The released guide gives this approval form:
cub variant approve --change-order <space>/<order> --stage <stage>
The server refuses a later stage until the earlier release has been approved and published.
Live status is also a write. This sends the observed revision and health to ConfigHub.
cub flux status . --cluster <cluster> --kube-context <kubectl-context> --watch
That reports for as long as you keep it running. cub flux apply also writes fluxbot.sh, which runs the same reporter on the cluster as one pod. It adds that pod to the Flux namespace and a permission in ConfigHub.
FLUX_CONTEXT=<kubectl-context> CLUSTER=<cluster> CONFIGHUB_URL=<ConfigHub address> bash onboard/fluxbot.sh
Read the Flux status details. A release, controller sync, and workload health are separate results; record the exact revision and target for each.