Chart Packaging & Registries
Packaging Helm charts for distribution, hosting them in repositories and OCI registries, and securing them with cryptographic signatures.
Introduction
A Helm chart starts its life as a directory of YAML templates, a Chart.yaml, and a values.yaml. That is perfectly fine for local development, but the moment you need to share a chart with teammates, deploy it across environments, or distribute it to customers, you need a packaging and distribution strategy. Helm provides a complete toolchain for this: helm package creates versioned archives, chart repositories and OCI registries host them, and provenance files verify their integrity.
This lesson covers the full lifecycle from local chart directory to signed, published artifact.
Creating Chart Archives with helm package
The helm package command takes a chart directory and produces a compressed .tgz archive named <name>-<version>.tgz. The name and version are read from Chart.yaml.
# Basic packaging
helm package ./mychart/
# Output: mychart-1.2.0.tgz
# Override version at package time
helm package ./mychart/ --version 2.0.0-rc1
# Write output to a specific directory
helm package ./mychart/ -d ./dist/
# Package with dependency update
helm package ./mychart/ -u
The -u flag runs helm dependency update before packaging, ensuring all subchart archives in charts/ are current. This is essential in CI pipelines where the charts/ directory may not be checked into version control.
What Gets Included
Everything in the chart directory is included in the archive except files matched by .helmignore. A typical .helmignore looks like this:
# .helmignore
.git
.gitignore
.helmignore
*.md
tests/
ci/
.env
secrets.yaml
*.swp
*.bak
__pycache__/
The syntax follows .gitignore conventions. Always exclude documentation, test fixtures, CI configuration, editor swap files, and anything containing secrets. A bloated chart archive slows down installs and wastes registry storage.
SemVer Versioning: version vs appVersion
Every Chart.yaml has two version fields, and confusing them is one of the most common Helm mistakes:
| Field | Tracks | When to Bump | Example |
|---|---|---|---|
version | The chart itself | Any change to templates, values, helpers, or dependencies | 1.2.0 |
appVersion | The deployed application | When the app image tag changes | "3.8.1" |
Both fields should follow Semantic Versioning. The version field is used by Helm to resolve dependencies, sort releases, and name archives. The appVersion is informational and commonly injected into image tags via .Chart.AppVersion.
version only. If you update the app image but nothing else in the chart changes, bump appVersion and version (since the chart contents did change).
Classic Chart Repositories
A classic Helm chart repository is simply an HTTP server hosting .tgz chart archives alongside an index.yaml file. The index file is a machine-readable catalog of every chart version available in the repository.
# index.yaml structure
apiVersion: v1
entries:
mychart:
- name: mychart
version: 1.2.0
appVersion: "3.8.1"
created: "2026-04-01T10:00:00Z"
digest: sha256:a1b2c3d4...
urls:
- https://charts.example.com/mychart-1.2.0.tgz
- name: mychart
version: 1.1.0
appVersion: "3.7.0"
created: "2026-03-15T08:30:00Z"
digest: sha256:e5f6a7b8...
urls:
- https://charts.example.com/mychart-1.1.0.tgz
Generate or update the index with:
helm repo index ./dist/ --url https://charts.example.com
Users interact with repos through these commands:
helm repo add myrepo https://charts.example.com
helm repo update
helm search repo myrepo/mychart
helm repo remove myrepo
ChartMuseum
ChartMuseum is a lightweight, open-source chart repository server. It auto-generates index.yaml and provides an upload API, eliminating the need to manually manage index files. It supports local storage, S3, GCS, Azure Blob, and other backends.
# Run with Docker
docker run -d -p 8080:8080 \
-e STORAGE=local \
-e STORAGE_LOCAL_ROOTDIR=/charts \
-v $(pwd)/charts:/charts \
chartmuseum/chartmuseum
# Upload a chart
curl --data-binary "@mychart-1.2.0.tgz" http://localhost:8080/api/charts
GitHub Pages
A popular free hosting option is to use a gh-pages branch (or the docs/ folder on main) as a chart repository. Package your charts, generate index.yaml with the GitHub Pages URL, commit, and push. GitHub serves the static files over HTTPS, which is all Helm needs.
helm package ./mychart/ -d ./docs/
helm repo index ./docs/ --url https://myorg.github.io/helm-charts
git add docs/ && git commit -m "Release mychart 1.2.0" && git push
Harbor
Harbor is an enterprise container registry that also serves as a chart repository. It supports both the classic chart repo protocol and OCI-based distribution, along with RBAC, vulnerability scanning, image signing, and cross-datacenter replication. If your organization already runs Harbor for container images, it is the natural choice for chart hosting.
OCI Registries
Starting with Helm 3.8, OCI (Open Container Initiative) support is stable and is now the recommended distribution method. OCI registries store charts as OCI artifacts alongside container images, eliminating the need for a separate index.yaml.
# Authenticate
helm registry login ghcr.io -u USERNAME
# Push (requires a .tgz file)
helm push mychart-1.2.0.tgz oci://ghcr.io/myorg/charts
# Pull
helm pull oci://ghcr.io/myorg/charts/mychart --version 1.2.0
# Install directly
helm install myrelease oci://ghcr.io/myorg/charts/mychart --version 1.2.0
# Show chart info
helm show chart oci://ghcr.io/myorg/charts/mychart --version 1.2.0
OCI registries that work with Helm charts include: GitHub Container Registry (ghcr.io), Docker Hub, Amazon ECR, Google Artifact Registry, Azure Container Registry, and Harbor. Any registry that supports OCI artifacts will work.
helm repo add and helm search repo. With OCI registries, there is no repo add step — you reference charts directly by their oci:// URL. The helm search command does not work with OCI; instead, use your registry's web UI or API to browse available charts.
Signing and Verifying Charts
Chart signing adds a cryptographic guarantee that a chart has not been tampered with and was produced by a trusted party. Helm uses GnuPG for signing.
# Package and sign in one step
helm package ./mychart/ --sign \
--key "Helm Signing Key" \
--keyring ~/.gnupg/secring.gpg
This produces two files: mychart-1.2.0.tgz (the chart archive) and mychart-1.2.0.tgz.prov (the provenance file). The .prov file contains the chart metadata, a SHA-256 digest of the archive, and a PGP signature.
# Verify a chart
helm verify mychart-1.2.0.tgz --keyring ~/.gnupg/pubring.gpg
# Install with automatic verification
helm install myrelease mychart-1.2.0.tgz \
--verify --keyring ~/.gnupg/pubring.gpg
When --verify is used, Helm will refuse to install the chart if the signature is missing, invalid, or signed by an untrusted key. In production environments, especially those subject to compliance requirements, chart verification should be mandatory.
secring.gpg) is required by Helm's signing implementation. If you use GnuPG v2, you may need to export your key to the legacy format: gpg --export-secret-keys > ~/.gnupg/secring.gpg.
CI/CD Pipeline for Chart Publishing
A well-structured CI/CD pipeline automates the entire chart lifecycle. A typical pipeline has four stages:
- Lint —
helm lint ./mychart/ --strictcatches syntax errors, missing required fields, and best-practice violations. - Test —
helm template ./mychart/ | kubeval --strictvalidates rendered manifests against the Kubernetes API schema. For integration tests, usect installfrom the chart-testing tool. - Package —
helm package ./mychart/ --version ${CI_TAG}creates the versioned archive. Optionally sign it with--sign. - Push —
helm push mychart-${CI_TAG}.tgz oci://ghcr.io/myorg/chartspublishes to the registry.
# GitHub Actions example
name: Release Chart
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
- name: Lint
run: helm lint ./mychart/ --strict
- name: Package
run: helm package ./mychart/ --version ${GITHUB_REF_NAME#v}
- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push
run: helm push mychart-${GITHUB_REF_NAME#v}.tgz oci://ghcr.io/${{ github.repository_owner }}/charts
Choosing a Distribution Strategy
The right distribution method depends on your team size, infrastructure, and security requirements:
| Method | Pros | Cons | Best For |
|---|---|---|---|
| GitHub Pages | Free, simple, version-controlled | Public only (unless GitHub Enterprise), manual index management | Open-source projects |
| ChartMuseum | Lightweight, auto-index, multi-backend storage | No RBAC, no scanning, extra infra to maintain | Small teams, internal use |
| Harbor | RBAC, scanning, replication, OCI + classic | Heavier to operate | Enterprise environments |
| OCI (ghcr.io, ECR, etc.) | No index.yaml, reuses container infra, modern standard | No helm search repo, newer ecosystem | Teams already using container registries |
Summary
helm package turns a chart directory into a versioned, distributable archive. Classic chart repositories serve .tgz files behind an index.yaml, while OCI registries use the same infrastructure as container images. ChartMuseum, Harbor, GitHub Pages, and cloud-hosted registries each serve different scales and security postures. Chart signing with provenance files provides tamper detection and origin verification. A robust CI/CD pipeline ties it all together: lint, test, package, sign, and push on every tagged release.
In the next lesson, we will look at advanced dependency management and library charts.