LearnKubernetes
Kubernetes12. Ecosystem & Tooling·EXPERT·8 min read

Helm

Helm

TL;DR

  • Helm is the package manager for Kubernetes: it templates raw YAML manifests with Go templating, bundles them into versioned, shareable units called charts, and tracks each deployed instance as a release.
  • Solves the problem of managing dozens of interrelated YAML files per application across multiple environments without hand-editing manifests per cluster.
  • helm rollback is reliable because Helm stores the fully-rendered manifest for every revision as a Secret in the release namespace — it never re-renders from source to roll back.
  • A helm upgrade only triggers a pod rollout if the rendered manifest content actually changes; a static :latest image tag with no other change means Kubernetes sees no diff and does nothing.
  • Values files beat long --set chains for anything beyond trivial overrides — reviewable, diffable, version-controllable.
  • Biggest trade-off: Go templating in YAML is powerful but fragile — whitespace, quoting, and type-coercion bugs in templates are a leading source of production incidents.

Core Concepts

Chart Anatomy

mychart/
├── Chart.yaml          # metadata: name, version, appVersion
├── values.yaml          # default configuration values
├── charts/              # dependency sub-charts
└── templates/
    ├── deployment.yaml
    ├── service.yaml
    ├── _helpers.tpl      # reusable named template snippets
    └── NOTES.txt         # post-install usage instructions shown to the user
# values.yaml
replicaCount: 3
image:
  repository: myorg/payments-api
  tag: "1.4.2"
resources:
  requests:
    cpu: 250m
    memory: 256Mi
# templates/deployment.yaml (Go templating references values.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-payments-api
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
      - name: app
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        resources:
          requests:
            cpu: {{ .Values.resources.requests.cpu }}
            memory: {{ .Values.resources.requests.memory }}

Templates are rendered client-side by the Helm binary (or Tiller in Helm 2, now removed) using Go’s text/template engine with Sprig functions added. The rendered output is plain YAML submitted to the Kubernetes API — Helm itself has no runtime component in the cluster in Helm 3.

Templating Engine

  • {{ .Values.x }} pulls from values.yaml (or overrides).
  • {{ .Release.Name }}, {{ .Release.Namespace }} expose release metadata.
  • {{ .Chart.Name }}, {{ .Chart.Version }} expose chart metadata.
  • _helpers.tpl defines named templates ({{ define "mychart.fullname" }}) invoked with {{ include "mychart.fullname" . }} — the standard mechanism for avoiding repeated logic across deployment.yaml, service.yaml, etc.
  • {{- ... -}} trims surrounding whitespace/newlines; omitting the hyphen is a common source of malformed YAML output after rendering.
  • Control structures ({{ if }}, {{ range }}, {{ with }}) let a single template conditionally emit different manifests based on values.

Values Precedence

Values are merged from multiple sources, later sources overriding earlier ones:

  1. Chart’s own values.yaml (lowest precedence)
  2. Parent chart’s values for a sub-chart
  3. -f / --values file(s), applied in the order given on the command line
  4. --set, --set-string, --set-file (highest precedence)

This layering is what enables one chart to serve dev, staging, and production via -f base-values.yaml -f prod-values.yaml.

Releases and Revisions

Every helm install creates release revision 1; every subsequent helm upgrade increments the revision number. Helm persists the fully-rendered manifest for each revision as a Secret (ConfigMap in older configurations) in the release’s namespace. This is what makes helm rollback deterministic and fast: rollback re-applies a previously stored rendered manifest rather than re-running templates against potentially-changed chart source.

Dependency Management

# Chart.yaml
dependencies:
- name: postgresql
  version: "12.x.x"
  repository: "https://charts.bitnami.com/bitnami"
  condition: postgresql.enabled

helm dependency update downloads dependency charts into charts/ and generates/updates Chart.lock. helm dependency build rebuilds charts/ strictly from an existing Chart.lock, giving reproducible installs across machines and CI runs (analogous to a lockfile in package managers).

Hooks

Charts can define lifecycle hooks (pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete, pre-rollback, post-rollback) via annotations on a Job or Pod manifest:

metadata:
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": before-hook-creation

Hooks run outside the normal release tracking of standard resources — a failed hook can block an upgrade, and hook resources are not automatically deleted unless a delete policy is set. Common use: DB migration Jobs that must complete before new pods roll out.

Repositories

Charts are distributed via chart repositories — an HTTP server hosting an index.yaml plus packaged .tgz charts. helm repo add, helm repo update, and helm search repo operate against this index. OCI registries (helm push oci://...) are now also a first-class distribution mechanism, replacing many standalone chart-repo servers.

Helm vs Kustomize

Aspect Helm Kustomize
Core mechanism Templating — Go templates generate YAML from variables Patching — overlays declaratively patch a base set of plain YAML manifests
Packaging model Versioned charts, shareable via repositories/OCI registries No packaging concept; just directories of YAML plus kustomization.yaml
Release tracking First-class releases/revisions, stored server-side as Secrets No release object; state is whatever is currently applied, tracked only by Git/CI history
Rollback helm rollback <release> <revision> — instant, built-in No native rollback; relies on re-applying a prior Git commit’s manifests
Templating logic Full programming constructs (if, range, functions) inside YAML No logic in YAML — only structural patches (strategic merge / JSON patch)
Environment overrides Layered values.yaml files merged by precedence Overlay directories per environment referencing a shared base/
Tooling footprint Standalone binary/CLI; charts often third-party Built directly into kubectl (kubectl apply -k) — no extra install
Ecosystem Large public chart ecosystem (Artifact Hub, Bitnami, etc.) Minimal ecosystem; expects hand-authored manifests
Learning curve Steeper — templating syntax, Sprig functions, hooks Shallower — plain YAML plus patch semantics
Best fit Distributing reusable, configurable third-party or internal packages Managing environment-specific variants of manifests already owned in-house

Command / Configuration Reference

# Install a chart as a new release
helm install payments-api ./mychart --namespace payments --create-namespace

# Override values at install time
helm install payments-api ./mychart -f production-values.yaml --set replicaCount=5

# Upgrade an existing release (in-place, versioned)
helm upgrade payments-api ./mychart --set image.tag=1.4.3

# Roll back to a previous revision instantly
helm rollback payments-api 2

# See revision history
helm history payments-api

# Uninstall
helm uninstall payments-api

# Render templates locally without installing anything (debugging)
helm template ./mychart --set replicaCount=5

# Dry-run an install/upgrade against the live cluster (validates against API, no changes applied)
helm upgrade payments-api ./mychart --dry-run --debug

# Lint a chart for structural/templating errors
helm lint ./mychart

# List all releases across all namespaces
helm list --all-namespaces

# Check exactly what values are currently applied to a live release
helm get values payments-api

# Fetch the fully-rendered manifest of a live release
helm get manifest payments-api

# Add and update a chart repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Search for charts in configured repositories
helm search repo bitnami/postgresql

# Dependency management
helm dependency update    # downloads dependency charts into charts/
helm dependency build     # rebuilds from Chart.lock for reproducible installs
# Chart.yaml
apiVersion: v2
name: mychart
description: Payments API chart
version: 0.3.1        # chart version (SemVer)
appVersion: "1.4.2"    # version of the app being deployed

Common Pitfalls

  • Pitfall: Overriding many nested values with a long chain of --set flags. Why: Comma-separated --set syntax is fragile for nested structures and lists, and produces no reviewable diff. Fix: Use a dedicated -f environment-values.yaml file checked into version control for anything beyond a single flat override.
  • Pitfall: Pinning container images to :latest in values.yaml. Why: With no content change in the rendered manifest, the Deployment controller has nothing to diff and won’t trigger a rollout on helm upgrade. Fix: Use immutable, unique tags (or digests) per build, or add a checksum/annotation that changes with each release.
  • Pitfall: Assuming helm rollback re-runs the original chart templates. Why: Rollback replays the previously stored rendered manifest for that revision, not the current chart source. Fix: Understand rollback restores exactly what was applied at that revision, even if chart source has since changed or been deleted — do not expect it to pick up unrelated fixes made to the chart afterward.
  • Pitfall: Forgetting {{- / -}} whitespace trimming in templates. Why: Go template output includes literal newlines/indentation from the template file, which can produce invalid or misindented YAML. Fix: Use whitespace-control hyphens consistently and validate with helm template / helm lint before applying.
  • Pitfall: Treating hook resources as automatically cleaned up. Why: Hook Jobs/Pods are not tracked as part of the release’s normal resource set and persist unless a helm.sh/hook-delete-policy is set. Fix: Set an explicit delete policy (e.g. before-hook-creation,hook-succeeded) on hook resources.
  • Pitfall: Editing charts/ (vendored dependency charts) directly. Why: helm dependency update overwrites the charts/ directory from the repository source, silently discarding local edits. Fix: Override sub-chart behavior via values passed from the parent chart, not by editing vendored files.

Interview Questions

  • A helm upgrade appears to succeed, but the running pods still show the old container image. What’s the most likely explanation? — Tests understanding that Kubernetes rollouts are driven by manifest diffs, not by Helm’s release bookkeeping.
  • Why does helm rollback work reliably even months after a release, when the original chart source might have changed or been deleted entirely? — Tests knowledge of how Helm persists rendered manifests as Secrets per revision.
  • Why is a values file preferred over long --set chains for complex overrides? — Tests practical judgment about maintainability and reviewability of configuration.
  • How does Helm resolve values when both a parent chart and a -f file set the same key? — Tests understanding of values precedence and merge order.
  • What is the difference between helm template and helm install --dry-run? — Tests knowledge of local rendering versus server-side validation against the live API.
  • When would Kustomize be a better fit than Helm for managing environment-specific manifests? — Tests ability to reason about templating versus patching trade-offs.
  • What happens to a chart’s hook resources after the hook completes? — Tests awareness that hooks live outside normal release resource tracking and require explicit cleanup policies.
🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
5 scenario questions on this topic
Take the quiz →
Related in 12. Ecosystem & Tooling
Private Docker Registry
ADVANCED
Kustomize
ADVANCED