Helm OCI Registries in Production: Package, Push, Verify, and Deploy by Digest
Ship Helm charts through OCI registries with reproducible packages, scoped registry credentials, provenance checks, immutable digests, and controlled promotion.
OCI registries provide a standard distribution path for Helm chart packages, but pushing a chart is only one step in a release. A production pipeline also needs to know exactly which chart was packaged, which registry identity can publish it, how consumers verify the package, and what immutable reference a deployment uses. Treat the packaged chart as a release artifact with a documented lifecycle, not as a directory that each environment rebuilds independently.
A chart, an OCI object, and a Helm release are different things. The chart is the portable package of templates and metadata. The registry stores and serves that package through an OCI reference. A release is Helm’s record of applying a chart and values to a cluster and namespace. Publishing a chart does not modify an existing release; a later upgrade applies a selected package and configuration to the cluster.
Understand OCI chart references
Assume Chart.yaml defines name: catalog and version: 1.4.2. After packaging, Helm names the archive catalog-1.4.2.tgz. Pushing to a registry path such as oci://ghcr.io/acme/helm-charts causes Helm to derive the destination chart name and version from chart metadata. The push destination names the registry and namespace, but omits the chart basename and version:
helm package ./charts/catalog --destination ./out
helm push ./out/catalog-1.4.2.tgz oci://ghcr.io/acme/helm-charts
For commands that retrieve a chart, include the chart name in the OCI reference. Request the version explicitly rather than relying on a registry’s idea of latest:
helm pull oci://ghcr.io/acme/helm-charts/catalog \
--version 1.4.2 \
--destination ./out
The chart’s semantic version becomes the OCI tag. appVersion is descriptive application metadata; it does not select the chart’s registry tag. Do not infer that a chart tag proves which container image is deployed. Pin workload images separately, preferably by image digest, and retain both chart and image identities in release evidence.
OCI references are not classic index.yaml chart-repository aliases. Registry authentication uses Helm’s registry login command; chart upload uses an OCI URI. Some registries require the destination namespace to exist before the first push, so provision and permission that path according to the provider’s documentation before making publication a release gate.
Build one package and preserve its identity
Build the exact package that will be reviewed, promoted, and deployed. Rebuild dependencies from the committed Chart.lock when the chart has dependencies, lint the source, render it with intended values, and package once:
set -euo pipefail
ARTIFACT_DIR="${RUNNER_TEMP:-/tmp}/chart-release"
mkdir -p "$ARTIFACT_DIR"
helm dependency build ./charts/catalog
helm lint ./charts/catalog
helm template catalog ./charts/catalog \
--values ./deploy/values/staging.yaml \
--kube-version "$KUBERNETES_VERSION" \
> "$ARTIFACT_DIR/rendered.yaml"
helm package ./charts/catalog --destination "$ARTIFACT_DIR"
PACKAGE="$ARTIFACT_DIR/catalog-1.4.2.tgz"
test -s "$PACKAGE"
tar -tzf "$PACKAGE"
sha256sum "$PACKAGE" > "$PACKAGE.sha256"
This example assumes the chart’s Chart.yaml version is 1.4.2, KUBERNETES_VERSION is a validated CI variable, and the chart has a dependency lock file. If there are no dependencies, omit the dependency build step. Fail if the expected archive is absent rather than selecting the first .tgz in a directory that might contain stale output.
Do not run dependency update in a release job unless changing dependency selection is part of the reviewed change. It can resolve versions again; dependency build uses the lock file to reproduce the recorded selection. Include the lock file and resolved subcharts according to the chart’s build process, and inspect the resulting package. A local template render is not API-server validation and does not prove that a target cluster has required CRDs or admission policies.
Keep rendered manifests only as test evidence when it is safe to retain them. Rendered output can include sensitive values if the chart templates Kubernetes Secrets. Restrict access or exclude sensitive output from general CI artifacts; do not upload production secret material casually.
Authenticate without leaving registry credentials behind
Registry login takes a host, optionally with a port, not a URL scheme or repository path. Helm normally writes credentials to its registry configuration file. On an ephemeral CI runner, use a dedicated temporary file, send the token through standard input instead of a command-line argument, and delete the file when the job exits:
set -euo pipefail
REGISTRY_CONFIG="$(mktemp "${RUNNER_TEMP:-/tmp}/helm-registry.XXXXXX")"
trap 'rm -f "$REGISTRY_CONFIG"' EXIT
printf '%s' "$REGISTRY_TOKEN" |
helm --registry-config "$REGISTRY_CONFIG" registry login "$REGISTRY_HOST" \
--username "$REGISTRY_USERNAME" \
--password-stdin
helm --registry-config "$REGISTRY_CONFIG" push \
"$PACKAGE" \
"oci://$REGISTRY_HOST/acme/helm-charts"
The CI system should expose REGISTRY_TOKEN only to the publishing job and grant it only the registry permission required for that operation. Prefer a short-lived identity flow when the registry supports it. Avoid printing credentials, enabling shell tracing around secrets, retaining the registry configuration as a general artifact, or sharing a write-capable credential with chart validation jobs that only need read access.
REGISTRY_HOST should be a host such as ghcr.io, not https://ghcr.io and not ghcr.io/acme/helm-charts. Keep the full OCI path in the push or pull reference. Use TLS verification. Flags that skip certificate validation or force plain HTTP belong only in isolated local testing, never in production as a way to silence a trust failure.
Verify what the registry stores
A successful Helm push reports a registry digest. Preserve it with the chart version, package checksum, source commit, Helm CLI version, and destination. These values answer different questions. SHA-256 of a local .tgz identifies archive bytes; an OCI digest identifies the registry manifest. They are not interchangeable hashes of the same object.
Pull the chart back into a clean workspace and verify that it can be read and rendered before treating it as a deployment input:
helm pull oci://ghcr.io/acme/helm-charts/catalog \
--version 1.4.2 \
--destination "$ARTIFACT_DIR"
helm show chart "$ARTIFACT_DIR/catalog-1.4.2.tgz"
helm template catalog "$ARTIFACT_DIR/catalog-1.4.2.tgz" \
--values ./deploy/values/staging.yaml \
--kube-version "$KUBERNETES_VERSION" \
> "$ARTIFACT_DIR/registry-rendered.yaml"
Compare the pulled archive checksum to the checksum recorded for the package that was pushed. If it differs, stop promotion and investigate instead of silently repackaging. Render from the pulled package, not the mutable source directory, so this test covers the object consumers will actually receive.
A registry digest is a stronger deployment reference than a tag: the tag is a name, while the digest identifies specific registry content. Helm supports OCI digest references. Record the exact digest reported at publication time as a validated pipeline output rather than parsing human-readable CLI output with a brittle regular expression. Keep the output in release metadata and make the deploy job require that validated value.
Deploy the exact OCI object
Use the full registry digest, including its sha256: prefix, in the deployment reference. The value below is a placeholder and must be replaced by the digest reported for the package that passed validation:
CHART_REF="oci://ghcr.io/acme/helm-charts/catalog@$CHART_DIGEST"
helm upgrade --install catalog "$CHART_REF" \
--namespace production \
--values ./deploy/values/production.yaml \
--dry-run=server \
--hide-secret
A server-side dry run requires cluster connectivity and exercises API-server validation without persisting the change. It is not a canary or a health check. Review the rendered change, check compatibility with persistent data and external systems, then use the approved rollout path. A deployment job can run the same digest-based command without the dry-run flag and with a deliberately selected wait policy and timeout. Do not assume rollback can reverse a database migration, external API call, or chart hook side effect.
Use the same digest in staging and production when the release is intended to be identical. If promotion copies a package into another registry, capture and verify the destination registry’s digest too; do not assume all registry copy or transformation paths preserve the same manifest digest. A digest identifies content, but retention, deletion, access policy, and tag mutability remain registry-specific operational concerns.
Add provenance and signature policy
For Helm’s GPG provenance workflow, sign the package and verify it against a controlled keyring before publication:
helm package ./charts/catalog \
--destination "$ARTIFACT_DIR" \
--sign \
--key "$SIGNING_KEY_NAME" \
--keyring "$SIGNING_KEYRING"
helm verify "$ARTIFACT_DIR/catalog-1.4.2.tgz" \
--keyring "$VERIFIER_KEYRING"
The .prov provenance file must be protected and distributed alongside the package. Helm documents that push uploads a neighboring provenance file as an additional OCI layer when present. Verification is meaningful only if the verifier obtained the expected public key through a trusted channel and applies an explicit signer policy. A valid signature shows that the package corresponds to a key; it does not prove that the chart templates are safe or that the signer should be trusted.
Helm also documents a Sigstore-based plugin for signing OCI charts. Treat a plugin signing path as an additional dependency: pin and review the plugin, define the trusted identity or key policy, and test both valid and invalid signatures before making verification a production gate. Registry digest integrity and signer identity solve different problems; a robust process needs both a pinned artifact identity and a trust decision for its publisher.
Account for Helm 4 documentation boundaries
The Helm command references document package, registry login, push, pull, verify, install, and upgrade interfaces. The Helm OCI topic guide warns that some of its content has not yet been updated for Helm 4. Check helm version –short in the release job and validate commands against references for that exact major version. Do not copy a command from an older blog post or assume that a third-party plugin built for Helm 3 is compatible with Helm 4.
Record the CLI version next to the chart digest and test package, login, push, pull, provenance verification, and digest-based deployment in a disposable registry and non-production cluster before rollout. One registry’s behavior does not establish another’s namespace creation, token scope, retention, or deletion semantics.
Production release checklist
- Build and test one versioned chart package from reviewed source and a dependency lock.
- Preserve the package, package checksum, source revision, Helm version, and registry digest as distinct evidence.
- Authenticate with a narrowly scoped, temporary credential and remove local auth state after the job.
- Push only to an approved registry namespace and enforce the intended policy for an already existing chart version.
- Pull the published chart into a clean workspace and render that package with target values.
- Verify provenance or signatures against an independently trusted signer policy where required.
- Deploy the exact registry digest, not a mutable tag or a fresh local rebuild.
- Test rollback and data compatibility; chart rollback does not undo external side effects.
- Confirm registry retention, backup, access, and garbage-collection behavior for deployed digests.
- Recheck commands and plugins against the exact Helm major version used by the release.
An OCI registry is most valuable when it becomes the stable handoff between chart build, verification, and deployment. Build once, identify the package and registry object precisely, validate after upload, and make each environment consume the same reviewed digest. That turns chart publication into a controlled software-supply-chain step rather than an informal file transfer.
Related:
- Helm Architecture and Release Lifecycle: Charts, Values, Hooks, Rollbacks, and Supply Chain
- GitHub Actions Reusable Workflows: Contracts, Permissions, and Safe Composition
Sources:
- Helm documentation: Use OCI-based registries
- Helm command reference: helm package
- Helm command reference: helm registry login
- Helm command reference: helm push
- Helm command reference: helm pull
- Helm command reference: helm verify
- Helm command reference: helm install
- Helm command reference: helm upgrade
- Helm command reference: helm template