Skip to content
7 min read·Lesson 7 of 8

Publishing Charts to OCI Registries

Modern chart distribution with OCI: helm push/pull, GHCR, ECR, Artifact Registry, Harbor, signing with Sigstore, and the legacy HTTP repo model.

Helm charts used to be published as static HTTP repos: an index.yaml manifest and a set of .tgz archives behind a CDN. Since Helm 3.8 (stable in 3.11), charts can be pushed to any OCI-compliant registry — the same registry you use for container images. This is now the default and recommended path.

Packaging

helm package ./my-app
# Successfully packaged chart and saved it to: my-app-1.4.2.tgz

The version comes from Chart.yaml; override with --version 1.4.2-rc.1. The output is a deterministic gzip-compressed tarball.

Pushing to an OCI Registry

The URL scheme is oci://<registry>/<namespace>. The chart name and version become part of the artifact reference:

helm registry login ghcr.io -u USERNAME -p TOKEN

helm push my-app-1.4.2.tgz oci://ghcr.io/acme/charts
# Pushed: ghcr.io/acme/charts/my-app:1.4.2
# Digest: sha256:9c2...

Pull and install directly from OCI:

helm pull oci://ghcr.io/acme/charts/my-app --version 1.4.2
helm install my-app oci://ghcr.io/acme/charts/my-app --version 1.4.2
helm show values oci://ghcr.io/acme/charts/my-app --version 1.4.2

You do not helm repo add for OCI — the URL is self-describing.

Cloud Registry Specifics

RegistryLogin
GitHub (GHCR)echo $GITHUB_TOKEN | helm registry login ghcr.io -u $USER --password-stdin
AWS ECRaws ecr get-login-password | helm registry login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com
Google Artifact Registrygcloud auth print-access-token | helm registry login -u oauth2accesstoken --password-stdin <region>-docker.pkg.dev
Azure ACRaz acr login --name <registry>
Harbor / ArtifactoryUsername + token / password

ECR requires the chart's repository path to exist as an ECR repository first (or, with newer ECR, with ecr:CreateRepository permission, it is created on first push).

CI/CD Pattern

A typical GitHub Actions workflow:

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write   # to push to GHCR
    steps:
      - uses: actions/checkout@v4
      - uses: azure/setup-helm@v4
        with: { version: 'v3.16.0' }
      - name: Lint
        run: helm lint ./charts/my-app
      - name: Package
        run: helm package ./charts/my-app
      - name: Login and push
        env: { GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} }
        run: |
          echo "$GITHUB_TOKEN" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
          helm push my-app-*.tgz oci://ghcr.io/${{ github.repository_owner }}/charts

Pin chart versions to your Git SHA or your release tag — never push 0.0.0-latest.

Signing Charts

OCI registries store everything as content-addressable artifacts, which makes signature attachment natural. Use Sigstore / cosign:

cosign sign --yes ghcr.io/acme/charts/my-app:1.4.2

# Verifier side:
cosign verify ghcr.io/acme/charts/my-app:1.4.2 \
  --certificate-identity-regexp 'https://github.com/acme/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

This is keyless signing: cosign uses your OIDC identity (GitHub Actions, GitLab, Google) and Sigstore's transparency log. No private keys to manage.

Helm's legacy helm package --sign uses PGP and a separate .prov file — works but few teams operate the key custody to make it useful. Cosign on OCI is the practical modern choice.

Legacy HTTP Repositories

You will still encounter the older model. Add a repo, then install:

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/postgresql
helm install pg bitnami/postgresql --version 13.4.1

Behind the scenes the repo serves an index.yaml file listing every chart and version with their .tgz URLs. Hosting is just static files — GitHub Pages, S3, GCS, NGINX all work. Many vendors still publish HTTP for compatibility but are dual-publishing to OCI.

Versioning Discipline

Treat chart versions as a published API:

  • Patch (1.4.2 → 1.4.3): bug fix, template adjustment, no values surface change.
  • Minor (1.4.x → 1.5.0): new feature, new values key, backwards compatible default.
  • Major (1.x → 2.0.0): values surface breaking change, removed keys, mandatory upgrade notes.

Document every change in CHANGELOG.md. Use artifacthub.io annotations in Chart.yaml if publishing publicly — they drive the Artifact Hub listing UI.

Internal Chart Catalogues

For organisations with many internal apps, run a central registry (Harbor, JFrog, Artifactory, ECR) and treat it like an internal npm. Pair with:

  • A chart linter and policy check (kubeconform, datreeio, kyverno) in CI
  • Mandatory maintainers and icon fields
  • Required JSON schema for values
  • Automated PR scaffolding from a chart template

The chart catalogue becomes the company's internal app-deploy contract.

Key Takeaways

  • OCI is the modern standard — charts live alongside container images in the same registry.
  • helm push and helm pull operate on oci:// URLs; helm repo add is the legacy HTTP path.
  • Authenticate to a registry once with helm registry login.
  • Sign charts with cosign (Sigstore) and verify on pull.
  • For private organisations, mirror via Harbor, JFrog Artifactory, or cloud-provider registries.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →