Lab Guide — Chart Packaging & Registries
Package a Helm chart, host it on ChartMuseum, and push it to an OCI registry.
Prerequisites
- Helm 3.12+ installed (
helm version) - Docker installed and running (for ChartMuseum and the OCI registry)
- A terminal with
curlandtaravailable
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.
.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
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
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:
- Local packaging —
helm package, version overrides,.helmignore, andindex.yamlgeneration. - ChartMuseum — running a chart repository server, uploading via API, searching and pulling with Helm.
- OCI registries —
helm pushandhelm pullwithoci://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.