A chart that lives only on your laptop is just a template library. A chart that is published to a registry becomes a versioned artifact that your entire organisation — and CI/CD pipelines, GitOps controllers, and on-call engineers at 3 a.m. — can pull, inspect, and deploy with a single command. This lesson covers everything from the classic HTTP chart repository protocol to modern OCI registries, semantic versioning discipline, and cryptographic provenance that makes auditors happy.
Two Distribution Models: HTTP Repos vs. OCI Registries
Helm supports two fundamentally different transport mechanisms for distributing charts.
Classic HTTP Repositories follow a simple contract: an HTTP server hosts a directory of .tgz chart archives and an index.yaml that lists every chart with its version, checksum, and download URL. Any static file server — S3, GCS, GitHub Pages, Nginx — can act as a Helm repo. You interact with it via helm repo add, helm repo update, and helm search repo.
OCI Registries (introduced as stable in Helm 3.8, released February 2022) store charts as OCI artifacts alongside container images in the same registry infrastructure. ECR, GCR, ACR, Harbor, and Docker Hub all support OCI artifacts. Instead of helm repo add you push and pull with helm push and helm pull oci://. No index.yaml is needed — the registry itself handles discovery and metadata.
Industry direction: Google, AWS, and Microsoft have all standardised on OCI registries as the preferred Helm distribution mechanism. If you are starting a new chart repository today, use OCI. HTTP repos are still common in open-source ecosystems (ArtifactHub, Bitnami) but new internal tooling at big-tech companies predominantly uses OCI.
Every Helm chart carries two independent version strings in Chart.yaml:
version — the chart version, semver-formatted. Bump this when the chart's templates, values schema, or defaults change.
appVersion — the version of the application the chart deploys (e.g. the Docker image tag). Changing appVersion without changing version is a common mistake that prevents meaningful rollbacks.
Strict semver discipline is non-negotiable in production:
MINOR (1.2.3 → 1.3.0) — new optional value added, new template feature, backwards-compatible change.
MAJOR (1.2.3 → 2.0.0) — breaking change: renamed value keys, removed template output, changed required field types. A major bump is a signal to consumers: your existing values.yaml overrides may break.
Production pitfall — the frozen appVersion: A team updates the Docker image tag in their deployment pipeline by passing --set image.tag=v2.3.1 at upgrade time but never bumps appVersion or version in Chart.yaml. After six months, helm list shows chart version 1.0.0 in every environment, making it impossible to correlate a cluster state with a Git commit. Always treat appVersion and version as part of your release artifact — automate their update in CI.
# Chart.yaml — two independent version fields
apiVersion: v2
name: myapp
description: Production-grade microservice chart
type: application
version: 1.4.2 # chart version — bump on template/values changes
appVersion: "2.3.1" # app version — matches Docker image tag
# In CI, automate version bumps with yq (a YAML processor)
# Bump chart version (minor) and sync appVersion with the image tag:
IMAGE_TAG="$(git rev-parse --short HEAD)"
yq e ".appVersion = \"${IMAGE_TAG}\"" -i charts/myapp/Chart.yaml
yq e ".version = \"$(semver bump minor $(yq e .version charts/myapp/Chart.yaml))\"" \
-i charts/myapp/Chart.yaml
Publishing to an HTTP Repository (S3 Pattern)
The S3 + helm repo index pattern is battle-tested for internal repos. The workflow is: package the chart, regenerate the index, and sync to S3. The Helm plugin helm-s3 automates this.
# One-time setup: install the helm-s3 plugin and initialise the bucket
helm plugin install https://github.com/hypnoglow/helm-s3.git
helm s3 init s3://my-company-charts/charts
# Add the repo locally (once per developer machine / CI runner)
helm repo add company s3://my-company-charts/charts
# Package and push a chart
helm package charts/myapp --destination /tmp/
helm s3 push /tmp/myapp-1.4.2.tgz company
# Verify
helm repo update
helm search repo company/myapp --versions
# NAME CHART VERSION APP VERSION DESCRIPTION
# company/myapp 1.4.2 2.3.1 Production-grade microservice chart
# company/myapp 1.4.1 2.2.8 ...
# Install a specific version (pinning is mandatory in production)
helm upgrade --install myapp company/myapp \
--version 1.4.2 \
--values prod-values.yaml \
--namespace myapp \
--atomic
Publishing to an OCI Registry (ECR Pattern)
AWS ECR is the most common OCI chart registry in enterprise environments. The workflow mirrors container image pushes — authenticate, package, push with a URI.
# Authenticate Helm to ECR (runs before every push in CI)
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=us-east-1
aws ecr get-login-password --region ${AWS_REGION} \
| helm registry login \
--username AWS \
--password-stdin \
${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
# Create the ECR repository for the chart (once)
aws ecr create-repository \
--repository-name helm-charts/myapp \
--region ${AWS_REGION}
# Package and push
helm package charts/myapp
helm push myapp-1.4.2.tgz \
oci://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/helm-charts
# Install directly from OCI (no helm repo add needed)
helm upgrade --install myapp \
oci://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/helm-charts/myapp \
--version 1.4.2 \
--values prod-values.yaml \
--namespace myapp \
--atomic
# Inspect chart metadata without installing
helm show chart \
oci://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/helm-charts/myapp \
--version 1.4.2
Immutability is a feature: Configure your OCI registry (or S3 bucket) to make published chart versions immutable — once myapp:1.4.2 is pushed, it cannot be overwritten. This mirrors Docker image best practices. If a bug is found, the fix goes into 1.4.3, never a silent overwrite of 1.4.2. ECR supports immutable image tags at the repository level; enable it with --image-tag-mutability IMMUTABLE in the create-repository command above.
Chart Provenance and Signing
For regulated industries (fintech, healthcare, government) and any supply-chain-conscious organisation, a chart's SHA digest is not enough — you need a cryptographic signature that proves the chart came from a trusted author and was not tampered with in transit. Helm supports signing via GPG provenance files.
When you run helm package --sign, Helm produces a .prov file alongside the .tgz. The provenance file contains the SHA-256 of the chart archive and a PGP-clearsigned block. Consumers run helm verify or pass --verify to helm install to validate the signature before deploying anything.
# Generate a GPG key for chart signing (do this once per org / team)
gpg --full-generate-key
# Choose: RSA 4096, no expiry (or a long expiry), name = "Helm Charts CI"
# Export the public key and distribute it to consumers (via keyserver or internal PKI)
gpg --export --armor "Helm Charts CI" > helm-signing-pub.asc
# Package and sign in CI
helm package charts/myapp \
--sign \
--key "Helm Charts CI" \
--keyring ~/.gnupg/pubring.gpg \
--destination /tmp/
# This produces two files:
# /tmp/myapp-1.4.2.tgz
# /tmp/myapp-1.4.2.tgz.prov
# Push both files to the chart repo
helm s3 push /tmp/myapp-1.4.2.tgz company
helm s3 push /tmp/myapp-1.4.2.tgz.prov company
# Consumers install with signature verification
helm install myapp company/myapp \
--version 1.4.2 \
--verify \
--keyring /etc/helm/helm-signing-pub.gpg
Sigstore / Cosign for OCI charts: GPG provenance works only with HTTP repos. For OCI registries, the emerging standard is Cosign (from the Sigstore project, backed by Google, Red Hat, and Chainguard). Cosign can sign OCI artifacts (including Helm charts pushed as OCI layers) with keyless OIDC-based signatures tied to a CI identity rather than a long-lived GPG key. This is the model used by major open-source projects (Kubernetes, cert-manager) today. Flux and ArgoCD both support Cosign verification of OCI chart sources.
CI/CD Integration: Automating the Full Publish Pipeline
The following GitHub Actions workflow ties together everything above: version bumping, packaging, pushing to ECR, and optionally signing — triggered on every push to main when the charts/ directory changes.
Chart Releaser for open-source projects: If you maintain a public chart repository on GitHub Pages, the chart-releaser action (helm/chart-releaser-action) automates packaging, GitHub Release creation, and index.yaml updates. It is the canonical setup for community chart repositories and is used by the Helm project itself.
Dependency Version Constraints
When your chart depends on subchart libraries (covered in lesson 6), the Chart.lock file pins exact versions of each dependency. Treat Chart.lock like package-lock.json — commit it, and run helm dependency update only when you intentionally upgrade a dependency. Never delete Chart.lock in CI and let it re-resolve freely; that is how you get a different dependency version in prod than in staging.
By the end of this lesson you have the tooling to close the gap between "chart on disk" and "versioned, signed, auditable artifact available to your entire organisation." The next lesson covers Helm hooks and chart tests — the mechanisms that let you validate deployments and perform database migrations atomically within a release.