1. Check a release before Argo CD or Flux takes it over
A chart that has run for months under helm upgrade can behave differently once a controller renders it. Some charts read a value back from the cluster with Helm's lookup, most often to keep a generated password. Argo CD and any pre-rendered path render without the cluster, so that value changes on every render. A Flux HelmRelease runs Helm in the cluster, so lookup works there.
Run the values check on the chart, version and values you use today. It renders the chart more than once and names every field that changes between renders of the same input. For a chart in a registry, pass its oci:// address in place of the name and leave out --repo.
New to cub? Install the cub CLI first. Public catalog packages pull and render anonymously, and you sign in only once a command saves or changes ConfigHub data.
cub plugin install confighub/cub-workshop@v0.6.61
cub config values grafana --repo https://grafana.github.io/helm-charts --version 10.5.15 \
--values my-values.yaml
For Grafana 10.5.15 with no admin password in the values, it reports that admin-password in the Secret and the checksum/secret annotation on the Deployment change on every render. Under Argo CD, each sync that applies a new render sets a new admin password and restarts the pod.
Supply the value yourself so the render stops changing. Each chart names that value differently, so search its defaults, for example helm show values <chart> | grep -n -i existingSecret. For Grafana, point admin.existingSecret at a Secret you create from the current password. Give it a new name and create it before the upgrade: the chart stops rendering its own Secret, so the next upgrade deletes that one. A chart that marks its Secret helm.sh/resource-policy: keep leaves it in place, so you can point at that Secret instead. Then render twice and compare; the diff should report 0 changed and exit 0.
helm template grafana grafana --repo https://grafana.github.io/helm-charts --version 10.5.15 -f my-values.yaml > render-1.yaml
helm template grafana grafana --repo https://grafana.github.io/helm-charts --version 10.5.15 -f my-values.yaml > render-2.yaml
cub config diff render-1.yaml render-2.yaml --exit-code
The two-render check finds a lookup only when its result changes. The values check also lists each lookup in the chart's source, with file and line, including one that comes back empty every time and so looks stable in every render. For Grafana 10.5.15 it lists three: the admin password in _helpers.tpl, and two in pvc.yaml behind persistence.lookupVolumeName. It reads the source only, so it cannot say what a lookup returns on your target.
One more change at handover shows in no single render. Argo CD names the Helm release after the Application unless spec.source.helm.releaseName is set. Many charts name their objects from the release, so set it to your current release name, or Argo CD creates a second set of objects beside the first. Argo CD applies the render without writing Helm's release record, so after handover helm list and helm rollback show the last Helm upgrade, not what runs. A Flux HelmRelease names its release [target namespace-]name unless you set it. Set releaseName, targetNamespace and storageNamespace to match the release Helm created, so Flux upgrades that release rather than installing a second one. Changing any of them later uninstalls the release before installing a new one.
The Flux CLI writes a HelmRelease that adopts the release. Use the release name and namespace that helm list -A shows.
flux create source helm grafana --url=https://grafana.github.io/helm-charts --export > source.yaml
flux create helmrelease grafana --source=HelmRepository/grafana --chart=grafana --chart-version=10.5.15 \
--release-name=grafana --target-namespace=monitoring --storage-namespace=monitoring \
--values=my-values.yaml --export > helmrelease.yaml
For Argo CD, write the Application yourself. Use the same release name and namespace, and put your fixed values under valuesObject. For a chart in an OCI registry, set repoURL to the registry path without oci://, for example registry-1.docker.io/cloudpirates. This Application sets no automated sync, so you can review the first diff in Argo CD before it applies.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: grafana
namespace: argocd
spec:
project: default
source:
repoURL: https://grafana.github.io/helm-charts
chart: grafana
targetRevision: 10.5.15
helm:
releaseName: grafana
valuesObject:
admin:
existingSecret: grafana-admin
destination:
server: https://kubernetes.default.svc
namespace: monitoring
Keep the values, the saved render and the diff beside your Application, and add the two-render comparison to CI. A chart upgrade that brings back a changing field then fails the build.
For an app a controller already manages, the Argo CD review task and the Flux review task review a change without replacing the controller.
To harden Argo CD itself before production, with single sign-on, RBAC and the admin account turned off, follow the Argo CD hardening Guide. It checks each setting and keeps the result as a variant of the Catalog base.
ConfigHub helps once the handover is done. It keeps the reviewed, stable render as the desired configuration, and Argo CD or Flux keeps delivering it as a release pulled by digest. Your later edits stay recorded changes through the chart's next version. The sections below show that delivery path; it needs an account or a server you run yourself.
2. Reconcile a published component now
Two reviewed components are already published to the public namespace. Point Flux at one with no account, and it reconciles.
flux create source oci nginx \
--url=oci://europe-west1-docker.pkg.dev/nth-fort-499605-q5/helm-expt/bitnami-nginx-rendered --tag=24.0.2
flux create kustomization nginx --source=OCIRepository/nginx --path="." --prune=true
This is proven end to end. Flux fetched digest sha256:ac21cf32 from the public URL and the nginx workload reached ready, in a cluster with no ConfigHub credentials. Redis is published the same way at bitnami-redis-rendered:25.5.3; provide its existing Secret before it reconciles.
ConfigHub runs that registry for this catalog, and every published version stays available at its tag and digest. Only nginx and Redis are pre-published as rendered images so far; every other chart page shows its own delivery receipts, and the Catalog says which ones have an Argo CD or Flux run behind them.
For any other catalog chart, render your own controller-native OCI and push it to a registry you control.
Here is what the command does. cub installer is an open-source plugin for the cub CLI. cub installer setup pulls a catalog package and writes its Kubernetes files locally, leaving delivery to kubectl, Argo CD or Flux. The generated scripts stop before doing any work when the plugin or kustomize is missing.
cub installer setup --pull oci://europe-west1-docker.pkg.dev/nth-fort-499605-q5/helm-expt/bitnami-nginx:24.0.2@sha256:7cf08c0348a32d577ffa0e16069ec6c2510ce773b372008d25b938f9546c5f67 \
--base http-clusterip --output-oci oci://YOUR-REGISTRY/reviewed-nginx:24.0.2
Argo CD reads the same output through an OCI Application, and kubectl applies the same files. A registry as source of truth records who pushed an artifact and when. It does not record whether the bytes were reviewed, or what objects change at the next version. Those are the digest-drift check and the render diff.
You can review a change before it reconciles, then build or govern a whole platform, with Flux still the reconciler.
3. Verify before you reconcile
A certified bundle carries its receipt as an attached record. Pull it by digest, verify it, then hand it to the reconciler. The workshop plugin does this for its shipped renders today, and any registry you control works, including a local one.
cub plugin install confighub/cub-workshop@v0.6.61
cub config check redis --out oci://YOUR-REGISTRY/redis:v1
cub config verify oci://YOUR-REGISTRY/redis@sha256:<digest from the line above>
cub config verify refuses an image that has no receipt, and it names any file whose bytes differ from what the receipt lists. What ConfigHub Workshop is says what a receipt covers and what it does not.
4. Render, inspect, then apply with kubectl
helm install renders and applies the chart in one command. The cub path splits that into render, inspect, then apply. The live Redis comparison checks all 13 chart objects field-for-field, runs both deployments, and records PONG from each.
1 · Render
cub installer setup writes plain files under ./out/manifests. The reuse-existing-secret preset records the Secret name and key the target must supply; it does not put password material in the rendered OCI.
2 · Apply
kubectl apply installs those files. Create the namespace first so the objects land where you expect.
Same render, same working result, visible before apply.
The same render, side by side with plain Helm:
# before either install: provide the password separately
$ kubectl create namespace redis
$ kubectl -n redis create secret generic redis-existing-secret \
--from-literal=redis-password="$(openssl rand -base64 32)"
# plain Helm with the preset's recorded values
$ helm install redis oci://registry-1.docker.io/bitnamicharts/redis \
--version 25.5.3 -n redis \
--set auth.existingSecret=redis-existing-secret \
--set auth.existingSecretPasswordKey=redis-password \
--set image.digest=sha256:6e7a020f1f6504698a7272c58783bdc2c23588c49febbae5aca1bb8dfa10af25
# or: render the reviewed package, write OCI, then apply
$ cub installer setup --pull oci://europe-west1-docker.pkg.dev/nth-fort-499605-q5/helm-expt/bitnami-redis:25.5.3@sha256:a216ce212424e05b341ef5000f1798e6014b72b8bc3dce9f315285871037af2a \
--base reuse-existing-secret --namespace redis \
--work-dir ./redis --non-interactive \
--output-oci ./redis-rendered.oci
$ kubectl apply -f ./redis/out/manifests -n redis
Now deploy it, three ways
Runs on your laptop until the apply: pushing the rendered layout to a registry needs only registry credentials, not a ConfigHub account.
First, put the rendered bundle somewhere a controller can pull: flux push artifact oci://REGISTRY/redis-rendered:v1 --path ./redis/out/manifests --source local --revision v1 (or oras cp the local layout).
kubectl
kubectl apply -f ./redis/out/manifests/
Flux
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata: { name: redis-rendered, namespace: flux-system }
spec:
interval: 5m
url: oci://REGISTRY/redis-rendered
ref: { tag: v1 }
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata: { name: redis-rendered, namespace: flux-system }
spec:
interval: 10m
sourceRef: { kind: OCIRepository, name: redis-rendered }
path: "."
prune: true
wait: true
Argo CD (3.x with OCI sources enabled)
Argo CD 2.x has no OCI source. On 2.x, pull the image with oras pull and commit the files to the repository Argo CD watches, or hand the image to Flux, whose OCIRepository reads it directly.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: redis-rendered, namespace: argocd }
spec:
project: default
source:
repoURL: oci://REGISTRY/redis-rendered
targetRevision: v1
path: .
destination: { server: https://kubernetes.default.svc, namespace: redis }
syncPolicy:
syncOptions: [CreateNamespace=true]
Pruning and CRD ordering differ per path. The GitOps adopter guide has the tested details; the CRD ordering record shows the failure you avoid.
5. Change an image without signing in
When an OCI already contains exact Kubernetes objects, you can change one named field and create a checked replacement locally. This example changes only the NGINX replica count, and it runs from a checkout of the helm-expt repository, the project behind this site:
$ npm run oci:transform -- \
oci://europe-west1-docker.pkg.dev/nth-fort-499605-q5/helm-expt/byo-nginx-ai-values@sha256:34af6a50b952d1a168a5cad614ef47f652cf44b11806a93bf6cc7a79c6e9c683 \
--object Deployment/nginx --namespace nginx \
--field spec.replicas --value 4 \
--output oci-layout:./nginx-replicas-4:reviewed
The output contains the complete Kubernetes YAML, the input digest, the exact field change, and the check results. The command pulls the output back and compares it before reporting success. Existing source and change records are kept when the output is changed again.
6. Check the record
Four receipts back this path, all anonymous. Flux reconciled the public nginx artifact and the workload reached ready, with no credential. A job with no ConfigHub login pulled the public package. Flux pulled a rendered output and the workload reached ready. Argo CD, Flux, and kubectl consumed one digest. The full manifests are on this page, above.
The live NGINX proof uses this installer output path with no ConfigHub token. Flux reconciled the exact output digest and the workload reached 1/1 ready replicas. The Redis comparison independently verifies a local rendered OCI and full Helm parity for the existing-Secret configuration.
source receipt -> object receipt -> delivery receipt -> runtime receipt
Each receipt proves one boundary. The runtime receipt reports what happened after delivery; it does not prove that the source or object set was correct.
7. Do this next
Reconcile the published nginx component, or render any other chart and hand the output to the Flux or Argo CD you already run. When the result needs shared variants, approvals, or a fleet rollout, upload it into ConfigHub and release it by digest.
Before you apply a chart with hooks, webhooks, or its own CRDs, read the current limits of rendering for Flux, Argo CD, or kubectl.