Releasing Charts to OCI Registries in CI
Modern Helm pushes charts to any OCI registry with helm push. Automate package, version-bump discipline, and publish in your pipeline.
The old world of a static index.yaml on a web server is fading. Since Helm 3.8, OCI registries are first-class — the same registry that stores your container images (GHCR, ECR, Docker Hub, Harbor, Artifactory) stores your charts too. One credential, one place, built-in auth and retention.
The publish flow
helm package ./mychartproduces a versioned.tgzfromChart.yaml'sversion.helm registry loginauthenticates to the registry (in CI, use a token from the pipeline's secrets).helm push mychart-1.4.0.tgz oci://<registry>/chartsuploads it. The chart name and version become the OCI repository and tag automatically.
Consumers then install straight from the registry: helm install my-api oci://ghcr.io/acme/charts/my-api --version 1.4.0 — no helm repo add required.
Versioning discipline is the real work
The commands are easy; the discipline is what separates a senior pipeline. Bump version (the chart's SemVer) on every change — patch for template tweaks, minor for new values, major for breaking value renames. Keep appVersion tracking the application image separately. Tag immutably: never overwrite an already-published chart version, because someone somewhere has pinned to it.
Sign what you publish.helm pushcan attach a provenance signature, and consumers canhelm install --verify. In a supply-chain-conscious org this is table stakes, not a nice-to-have.
Example
# CI step: package and publish a chart to an OCI registry (GitHub Actions style)
set -euo pipefail
# Fail early if the chart version was not bumped for this change
ct lint --charts ./charts/my-api --check-version-increment=true
# Package -> the .tgz is named from Chart.yaml's version
helm package ./charts/my-api --destination ./dist
# Authenticate using a CI-provided token, never a hard-coded password
echo "$REGISTRY_TOKEN" | helm registry login ghcr.io \
--username "$GITHUB_ACTOR" --password-stdin
# Push -- chart name + version become the OCI repo and tag
helm push ./dist/my-api-1.4.0.tgz oci://ghcr.io/acme/charts
# Consumers install directly, no 'helm repo add' needed:
# helm install my-api oci://ghcr.io/acme/charts/my-api --version 1.4.0When to use it
- A CI pipeline packages and pushes a versioned chart to GitHub Container Registry on every release tag so users can install it with a helm install oci:// command.
- A platform team uses OCI registries to store charts alongside container images in the same ECR repository, simplifying access control to a single IAM policy.
- A developer pins a chart version from an OCI registry in Chart.yaml dependencies so builds are reproducible regardless of which charts are currently pushed to the registry.
More examples
Package and push in GitHub Actions
Authenticates to GitHub Container Registry, reads the chart version from Chart.yaml, and pushes the package as an OCI artifact.
# In a GitHub Actions step:
echo "${{ secrets.GITHUB_TOKEN }}" | \
helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
VERSION=$(grep '^version:' Chart.yaml | awk '{print $2}')
helm package ./myapp
helm push myapp-${VERSION}.tgz oci://ghcr.io/${{ github.repository_owner }}/chartsInstall from OCI registry
Installs a specific chart version directly from an OCI registry without adding a repository first.
helm install my-app \
oci://ghcr.io/myorg/charts/myapp \
--version 1.3.0 \
-f values-prod.yaml \
-n productionOCI dependency in Chart.yaml
Declares an OCI-hosted chart as a dependency using an oci:// repository URL, supported since Helm 3.8.
# Chart.yaml
dependencies:
- name: mylib
version: "1.0.0"
repository: oci://ghcr.io/myorg/charts
Discussion