Lesson 18 Chart Packaging & Registries
Theme:

Lab Guide — Chart Packaging & Registries

Package a Helm chart, host it on ChartMuseum, and push it to an OCI registry.

Prerequisites

Setup

Create a sample chart that we will use throughout the exercises:

mkdir -p ~/helm-lab18 && cd ~/helm-lab18
helm create webapp

Edit webapp/Chart.yaml so the versions are explicit:

apiVersion: v2
name: webapp
description: A sample chart for packaging lab
type: application
version: 1.0.0
appVersion: "1.0.0"

Create a .helmignore in the chart directory:

cat > webapp/.helmignore <<'EOF'
.git
.gitignore
*.md
tests/
ci/
.env
*.swp
*.bak
__pycache__/
EOF

Exercise 1 — Package a Chart and Inspect the Archive

In this exercise you will package the chart, examine its contents, and verify the SemVer version.

Step 1: Package the chart

cd ~/helm-lab18
helm package ./webapp/

You should see output like: Successfully packaged chart and saved it to: /home/user/helm-lab18/webapp-1.0.0.tgz

Step 2: Inspect the archive contents

# List the files inside the .tgz
tar tzf webapp-1.0.0.tgz

Verify the archive contains Chart.yaml, values.yaml, the templates/ directory, and does not contain any files matched by .helmignore (e.g., no .md files).

Step 3: Override the version at package time

helm package ./webapp/ --version 1.1.0-beta1 -d ./dist/

Check that the output archive is named webapp-1.1.0-beta1.tgz and is placed in the ./dist/ directory.

Step 4: Verify Chart.yaml inside the override archive

tar xzf ./dist/webapp-1.1.0-beta1.tgz webapp/Chart.yaml -O

Confirm the version field reads 1.1.0-beta1 while appVersion remains "1.0.0".

Step 5: Generate an index.yaml

mkdir -p ./repo
cp webapp-1.0.0.tgz ./repo/
cp ./dist/webapp-1.1.0-beta1.tgz ./repo/

helm repo index ./repo/ --url https://charts.example.com

Open ./repo/index.yaml and verify it contains two entries under entries.webapp with the correct versions and digests.

Checkpoint: You should have two .tgz archives with different versions and a valid index.yaml that catalogs both.

Exercise 2 — Set Up a Local ChartMuseum and Push/Pull Charts

Run a local ChartMuseum instance, upload charts via its API, and install from it using Helm.

Step 1: Start ChartMuseum with Docker

mkdir -p ~/helm-lab18/chartmuseum-storage

docker run -d --name chartmuseum \
  -p 8080:8080 \
  -e STORAGE=local \
  -e STORAGE_LOCAL_ROOTDIR=/charts \
  -v ~/helm-lab18/chartmuseum-storage:/charts \
  chartmuseum/chartmuseum

Verify it is running:

curl -s http://localhost:8080/health
# Expected: {"healthy":true}

Step 2: Upload a chart

curl --data-binary "@webapp-1.0.0.tgz" http://localhost:8080/api/charts

Expected response: {"saved":true}

Step 3: Add ChartMuseum as a Helm repo

helm repo add local-museum http://localhost:8080
helm repo update

Step 4: Search and inspect the chart

helm search repo local-museum/webapp
helm show chart local-museum/webapp

Confirm the chart version 1.0.0 appears in the search results.

Step 5: Upload the second version

curl --data-binary "@./dist/webapp-1.1.0-beta1.tgz" http://localhost:8080/api/charts
helm repo update
helm search repo local-museum/webapp --versions

You should now see both 1.0.0 and 1.1.0-beta1 listed.

Step 6: Pull a specific version

helm pull local-museum/webapp --version 1.0.0 -d ./pulled/
ls ./pulled/

Verify webapp-1.0.0.tgz was downloaded.

Step 7: Clean up

docker stop chartmuseum && docker rm chartmuseum
helm repo remove local-museum
Checkpoint: You successfully ran a chart repository server, uploaded two chart versions via the API, searched and pulled charts using standard Helm commands.

Exercise 3 — Push to an OCI Registry with helm push

Use a local Docker registry to practice the OCI workflow: push, pull, and install charts using oci:// URLs.

Step 1: Start a local OCI registry

docker run -d --name oci-registry -p 5000:5000 registry:2

Step 2: Push the chart

helm push webapp-1.0.0.tgz oci://localhost:5000/helm-charts

Expected output includes the manifest digest and the chart URL. Note that helm push requires a .tgz file, not a chart directory.

Step 3: Verify the chart is in the registry

# List tags via the registry API
curl -s http://localhost:5000/v2/helm-charts/webapp/tags/list

You should see {"name":"helm-charts/webapp","tags":["1.0.0"]}.

Step 4: Pull from OCI

mkdir -p ./oci-pulled
helm pull oci://localhost:5000/helm-charts/webapp --version 1.0.0 -d ./oci-pulled/
ls ./oci-pulled/

Step 5: Show chart metadata from OCI

helm show chart oci://localhost:5000/helm-charts/webapp --version 1.0.0

Confirm the name, version, and appVersion match what you packaged.

Step 6: Template from OCI (dry-run install)

helm template mywebapp oci://localhost:5000/helm-charts/webapp --version 1.0.0

This renders the templates locally without a cluster. Verify the YAML output looks correct.

Step 7: Push the second version

helm push ./dist/webapp-1.1.0-beta1.tgz oci://localhost:5000/helm-charts

# Verify both tags exist
curl -s http://localhost:5000/v2/helm-charts/webapp/tags/list

Expected: {"name":"helm-charts/webapp","tags":["1.0.0","1.1.0-beta1"]}

Step 8: Clean up

docker stop oci-registry && docker rm oci-registry
Checkpoint: You pushed and pulled charts using the OCI protocol with oci:// URLs, verified tags via the registry API, and rendered templates directly from the registry.

Summary

In this lab you practiced the three core distribution workflows:

  1. Local packaging — helm package, version overrides, .helmignore, and index.yaml generation.
  2. ChartMuseum — running a chart repository server, uploading via API, searching and pulling with Helm.
  3. OCI registries — helm push and helm pull with oci:// URLs using a local Docker registry.

These patterns translate directly to production: swap the local registry for ghcr.io, ECR, or Harbor and the workflow remains the same.