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 rollbackis 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 upgradeonly triggers a pod rollout if the rendered manifest content actually changes; a static:latestimage tag with no other change means Kubernetes sees no diff and does nothing. - Values files beat long
--setchains 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 fromvalues.yaml(or overrides).{{ .Release.Name }},{{ .Release.Namespace }}expose release metadata.{{ .Chart.Name }},{{ .Chart.Version }}expose chart metadata._helpers.tpldefines named templates ({{ define "mychart.fullname" }}) invoked with{{ include "mychart.fullname" . }}— the standard mechanism for avoiding repeated logic acrossdeployment.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:
- Chart’s own
values.yaml(lowest precedence) - Parent chart’s values for a sub-chart
-f/--valuesfile(s), applied in the order given on the command line--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
--setflags. Why: Comma-separated--setsyntax is fragile for nested structures and lists, and produces no reviewable diff. Fix: Use a dedicated-f environment-values.yamlfile checked into version control for anything beyond a single flat override. - Pitfall: Pinning container images to
:latestinvalues.yaml. Why: With no content change in the rendered manifest, the Deployment controller has nothing to diff and won’t trigger a rollout onhelm upgrade. Fix: Use immutable, unique tags (or digests) per build, or add a checksum/annotation that changes with each release. - Pitfall: Assuming
helm rollbackre-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 withhelm template/helm lintbefore 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-policyis 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 updateoverwrites thecharts/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 upgradeappears 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 rollbackwork 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
--setchains for complex overrides? — Tests practical judgment about maintainability and reviewability of configuration. - How does Helm resolve values when both a parent chart and a
-ffile set the same key? — Tests understanding of values precedence and merge order. - What is the difference between
helm templateandhelm 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.