Helm is described as “the package manager for Kubernetes”, which is accurate and undersells the part that matters most in production. Templating is the feature people notice; release management is the feature they depend on.
Topic 1: The Problem Helm Solves
A single service needs a Deployment, a Service, an Ingress, a ConfigMap, an HPA, a PDB and a ServiceAccount. Multiply by three environments and you have a directory tree where the same image tag appears in nine places, and no way to answer “what is different about staging” except by diffing directories.
Helm does three separate jobs:
- A template engine. One set of manifests, parameterised by values.
- A package format. A versioned, shareable
.tgzwith declared dependencies — so “install Postgres” is one line rather than a manifest hunt. - A release manager. Helm records what it installed, which is what makes
upgrade,rollback,uninstallanddiffpossible at all.
The third job is the one you cannot get from envsubst, sed, or kubectl apply -k. Without stored state, “undo the last deploy” means reconstructing what the previous manifests were.
Topic 2: Helm 3 Has No Server
Helm 2 ran an in-cluster component called Tiller with, in most installations, cluster-admin. Helm 3 removed it entirely. The consequences:
- Helm can do exactly what your kubeconfig can do. No privilege escalation path through a shared server, and RBAC applies to you personally.
- Rendering is local.
helm templateproduces the same YAML whether or not a cluster exists. - Release state lives in the cluster as Secrets, in the release’s namespace, one per revision.
kubectl get secret -n prod -l owner=helm
# sh.helm.release.v1.checkout.v1 helm.sh/release.v1 1 3d
# sh.helm.release.v1.checkout.v2 helm.sh/release.v1 1 2h
Each Secret holds the chart, the supplied values and the rendered manifest, gzipped and base64-encoded twice. You can read one by hand:
kubectl get secret sh.helm.release.v1.checkout.v2 -n prod \
-o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | head -40
Anything still telling you to run helm init or configure Tiller is describing Helm 2, which reached end of life in 2020. That includes a large fraction of the tutorials and course material still in circulation — worth knowing so you can date a document in one glance.
Two practical implications of storing state in Secrets: a release is scoped to a namespace, not a cluster (so the same chart can be installed many times under different names), and a large chart with many revisions consumes etcd space — which is what --history-max exists to bound.
Topic 3: The Vocabulary, Precisely
Four words that get used interchangeably and should not be:
| Term | What it is |
|---|---|
| Chart | The package: templates, default values, metadata. A directory or a .tgz. |
| Values | The configuration supplied at install time, merged over the chart’s defaults. |
| Release | One installation of a chart into a namespace, under a name. |
| Revision | One version of a release. Every install, upgrade and rollback creates a new one. |
helm install checkout ./chart -n prod # release "checkout", revision 1
helm upgrade checkout ./chart -n prod # revision 2
helm install checkout-canary ./chart -n prod # a SECOND release of the same chart
That last line is the one worth internalising: a chart can be installed many times in one namespace. It is why every resource name in a well-written chart is derived from .Release.Name, and why a chart with hardcoded resource names fails the second time anyone tries to use it.
Topic 4: The Commands That Cover 90% of Use
# Find and inspect before installing
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo postgres
helm show values bitnami/postgresql | head -50 # the interface you get to configure
helm show chart bitnami/postgresql
# Install, upgrade, inspect
helm install checkout ./chart -n prod --create-namespace
helm upgrade --install checkout ./chart -n prod -f values-prod.yaml
helm list -n prod
helm status checkout -n prod
helm history checkout -n prod
# What did I actually deploy?
helm get values checkout -n prod # values YOU supplied
helm get values checkout -n prod -a # ...merged with the chart defaults
helm get manifest checkout -n prod # the rendered YAML, as applied
helm get hooks checkout -n prod
helm get notes checkout -n prod
# Undo
helm rollback checkout 2 -n prod
helm uninstall checkout -n prod
helm upgrade --install is the form to use in automation: it installs if the release is absent and upgrades if it is present, so the pipeline needs no branching and re-running it is harmless.
helm show values before installing a third-party chart is the habit that separates people who configure charts from people who copy someone’s values file from a blog post. It is the chart’s public interface, and it is the documentation.
Topic 5: What Helm Is Not
Three misconceptions worth clearing early, because each one leads to a different kind of disappointment:
Helm is not a controller. It does not reconcile continuously. After helm upgrade returns, Helm stops caring; if someone edits a Deployment by hand, nothing corrects it until the next upgrade. Continuous reconciliation is what Argo CD and Flux add — covered in the delivery lesson.
Helm does not validate your Kubernetes objects. It renders text. A misspelled field renders happily and is rejected by the API server, which is why --dry-run=server exists and why client-side rendering alone proves less than it appears to.
Helm is not the only option, and not always the right one. Kustomize does overlays with no templating language and no release state; plain manifests are fine for one environment; an operator is the answer when the thing needs ongoing reconciliation logic. The Kubernetes path’s Helm and Kustomize lesson compares them directly rather than duplicating the argument here.
The rough decision: Helm when you are distributing something for others to install, or when you need release history and rollback. Kustomize when you own the manifests and want overlays without a template language. Many teams use both — Helm for third-party dependencies, Kustomize for their own workloads.
Topic 6: Installing Helm and Making It Pleasant
brew install helm # macOS
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
helm env # where Helm keeps its cache and config
Two plugins that most teams end up installing, and one setting:
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade checkout ./chart -f values-prod.yaml # see the change first
helm plugin install https://github.com/jkroepke/helm-secrets
helm completion zsh > "${fpath[1]}/_helm" # tab-complete release names
helm-diff is the one to install today. Running an upgrade without seeing the diff is the Helm equivalent of terraform apply without reading the plan, and the plugin turns that into one command.
Try it yourself: install a chart, then answer three questions using only kubectl: which revisions exist, what manifest revision 1 contained, and which values were supplied. Doing it once without Helm’s own commands makes the storage model concrete — and it is exactly what you will fall back on when a release is stuck and helm itself is unhappy.
Common mistake: treating helm install as a one-way operation and reaching for kubectl edit when something needs changing. The edit works, the next helm upgrade silently reverts it, and the person who made the edit concludes that Helm is unreliable. Every change belongs in the chart or in the values — the three-way merge lesson explains exactly why the edit disappears.