Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Create and publish a Helm chart

Learn how to create and publish a Helm chart in this hands-on Kubernetes tutorial. Step-by-step guide with troubleshooting tips and next steps.

Focus: create and publish a helm chart

Sponsored

If you have ever deployed the same Kubernetes manifest across staging and production, only to find you missed a replica count or an image tag, you already understand the pain Helm solves. Without a templated, versioned packaging system, your YAML files become a swamp of copy-paste errors, inconsistent configs, and manual toil. This lesson teaches you how to create and publish a Helm chart — the standard way to bundle, configure, and distribute Kubernetes applications so your team can ship with confidence, not YAML fatigue.

The problem this lesson solves

Raw Kubernetes manifests are static. You cannot easily parameterise a Deployment spec for different environments, embed release metadata, or share a complete app stack with your colleagues without zipping and emailing YAML files. Helm charts solve this by introducing:

  • Templates that inject values at install time (e.g., {{ .Values.replicaCount }})
  • Built-in release tracking via Helm secrets
  • Repository publishing so anyone can helm install your app
  • Dependency management for microservice stacks

Without a chart, your deployment process is fragile. With one, you unlock reproducibility, upgradability, and sharability — the difference between a script and a platform.

Core concept / mental model

Think of a Helm chart as a software installer for Kubernetes — like a .deb or .msi package, but for containerised apps. The chart is a directory structure that holds:

Component Purpose
Chart.yaml Metadata (name, version, description)
values.yaml Default configuration values
templates/ Go-templated Kubernetes resource files
charts/ Sub‑chart dependencies (optional)

Mental model: values.yaml is the control panel. templates/ are the blueprints. helm install feeds the control-panel settings into the blueprints to stamp out real Kubernetes objects.

How it works step by step

  1. Scaffold the chart structure with helm create <chart-name>. Helm generates a starter skeleton with a Deployment, Service, HPA, and Ingress template.
  2. Edit Chart.yaml — set name, version, appVersion, and description. This metadata appears in helm list and repository indexes.
  3. Define defaults in values.yaml — expose every tunable parameter: image registry, replica count, port, environment variables.
  4. Write or modify templates — use Go’s template syntax ({{ .Values.replicaCount }}), helper functions from _helpers.tpl, and control structures like {{- if .Values.ingress.enabled }}.
  5. Validate locally — run helm lint to catch YAML syntax errors, then helm template . to preview rendered manifests.
  6. Packagehelm package . creates a .tgz archive with a bundled Chart.lock if you have dependencies.
  7. Publish — upload the .tgz to a Helm repository (e.g., OCI registry via helm push or a static index.yaml on GitHub Pages, S3, or ChartMuseum).

Hands-on walkthrough

1. Create a minimal chart from scratch

mkdir myapp-chart && cd myapp-chart
cat > Chart.yaml << 'EOF'
apiVersion: v2
name: myapp
version: 0.1.0
appVersion: "1.0.0"
description: A simple NGINX web app
EOF

mkdir templates
cat > templates/deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ include "myapp.name" . }}
  template:
    metadata:
      labels:
        app: {{ include "myapp.name" . }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.port }}
EOF

cat > values.yaml << 'EOF'
replicaCount: 1
image:
  repository: nginx
  tag: latest
service:
  port: 80
EOF

Pro tip: Use helm create for a full scaffold, then delete what you do not need. It saves keystrokes and gives you a standard file layout.

2. Lint and render

helm lint .
# == Results: 0 chart(s) linted, 0 chart(s) failed

helm template .
# ---
# Source: myapp/templates/deployment.yaml
# ---
# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   name: release-name-myapp
# ... manifests printed to stdout

The helm template output shows exactly what will be submitted to the cluster — great for debugging.

3. Package the chart

helm package .
# Successfully packaged chart and saved it to: /tmp/myapp-chart/myapp-0.1.0.tgz

4. Publish to an OCI registry (e.g., GitHub Container Registry)

echo $GITHUB_TOKEN | helm registry login ghcr.io -u $GITHUB_USERNAME --password-stdin

helm push myapp-0.1.0.tgz oci://ghcr.io/$GITHUB_USERNAME/helm-charts
# Pushed: ghcr.io/myuser/helm-charts/myapp:0.1.0

Now any authorised user can install directly from the OCI registry:

helm install myapp oci://ghcr.io/myuser/helm-charts/myapp --version 0.1.0

Compare options / when to choose what

Approach Best for Drawback
static index.yaml (GitHub Pages) Open‑source, public charts Manual index update after push
OCI registry (GHCR, ECR, ACR) Teams already using container registries Requires auth for private repos
ChartMuseum Self‑hosted private repos Separate server to maintain
ArtifactHub Discovering and sharing public charts Not a hosting platform (indexes others)

Mental rule: If your team already has a container registry, use OCI — it is the path Helm 3 natively supports. For fully open-source projects, GitHub Pages with an index is free and frictionless.

Troubleshooting & edge cases

  • helm lint passes but helm install fails – The template references a value that evaluates to an empty string, producing invalid YAML. Use helm template . | kubectl apply --dry-run=client -f - to let kubectl validate.
  • Chart contains too many optional resources – Use a templates/NOTES.txt to print post‑install instructions. Keep _helpers.tpl for reusable named templates.
  • Dependency chart not found – Run helm dependency update to fetch sub‑charts into charts/ and regenerate Chart.lock.
  • Publish to OCI fails with unauthorized – Ensure you logged in with a token that has read:packages and write:packages scopes.
  • Chart version already exists – Helm enforces semantic versioning. Bump the version field in Chart.yaml before pushing again.

What you learned & what's next

You now understand how to create and publish a Helm chart — from scaffolding with helm create to packaging and pushing to an OCI registry. You can:

  • Parameterise Kubernetes manifests with values.yaml
  • Validate chart syntax with helm lint and preview output with helm template
  • Publish a chart as a .tgz to a registry so anyone can install it with helm install

The next lesson in this track builds on your publishing skills: managing chart dependencies and upgrading releases — where you will handle multiple sub‑charts and rollback safely.

Practice recap

As a quick exercise, create a chart for a simple NGINX web server with environment‑specific replica counts. Use helm create webapp, customise values.yaml to expose replicaCount and image.tag, then lint and package it. Push the chart to a local ChartMuseum or a free OCI registry like GitHub Container Registry to verify the full publish workflow.

Common mistakes

  • Forgetting to bump the version field in Chart.yaml before repackaging — Helm rejects duplicate package versions.
  • Using hard-coded values inside templates instead of {{ .Values.* }} — the chart is not reusable across environments.
  • Publishing a chart with helm lint errors — always run helm lint . and helm template . first to catch YAML issues early.
  • Pushing to an OCI registry without prior helm registry login — you get silent authentication failures.

Variations

  1. Publish to a static index.yaml on GitHub Pages instead of an OCI registry — simpler for public open-source charts.
  2. Use Chart.lock for reproducible builds with sub‑chart dependencies, then helm dependency build in CI.
  3. Adopt kubectl krew plugins or Helmfile to orchestrate multiple chart installs — not a direct substitute for publishing, but common in enterprise setups.

Real-world use cases

  • Publishing a microservice chart to an internal Helm registry so every team installs the same app version across dev/staging/prod.
  • Sharing an open-source monitoring stack (e.g., Prometheus + Grafana) via a public OCI chart on GitHub Container Registry.
  • Distributing a vendor app (like a CI/CD runner) as a Helm chart so customers self-install with their own config values.

Key takeaways

  • A Helm chart is a packaged directory of Go-templated YAML files plus a Chart.yaml manifest.
  • Scaffold with helm create, not from scratch — it gives you a standard structure and _helpers.tpl.
  • Check rendering before deployment: helm lint for syntax, helm template for output preview.
  • Package with helm package . — the .tgz filename must match Chart.yaml version.
  • Publish to an OCI registry with helm push for native auth and versioning, or to a static index.yaml for simplicity.
  • Always bump the chart version on every change to avoid push conflicts and enable helm upgrade.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.