Phase 4 · ORCHESTRATION & IAC
Helm — charts, values, releases (Go Deeper: GitOps with Argo CD)
By the end of today
- Scaffold a chart with helm create, then lint and template it locally
- Install, upgrade with --set, and roll back a named Helm release
- Explain how templates plus values render into the manifests Helm applies
Charts, values and releases: Kubernetes’ package manager
Yesterday you exposed an app with an Ingress by hand, kubectl apply -f one manifest at a time. That works for one file. But a real service is rarely one file — a Deployment, Service, Ingress, ConfigMap, ServiceAccount and HPA, a dozen manifests that must stay in step and that you redeploy, slightly differently, across dev, staging and prod. Hand-editing near-identical YAML per environment is how drift creeps in. Helm is the fix: the package manager for Kubernetes.
Three words carry the whole model. A chart is a versioned package of templated manifests — the YAML you already write, with the parts that change per environment pulled out into placeholders. A values file (values.yaml) holds the defaults for those placeholders, and you override them at deploy time with --set key=value or your own -f prod-values.yaml. A release is what you get when you install a chart: a named, tracked instance living in the cluster. Install the same chart three times under three names and you have three independent releases.
The mechanism is refreshingly unmagical. Helm merges your values, runs every file in templates/ through Go’s templating engine, and the result is ordinary Kubernetes YAML — the exact manifests you would have written by hand. It sends that to the API server. helm template stops at the render step and prints the YAML without touching the cluster, which makes it ideal for reviewing a change or diffing it in CI.
Releases are what make Helm more than a templating tool. helm install records revision 1. Every helm upgrade renders again and bumps the revision. helm history lists them, and helm rollback demo 1 returns to an earlier state — crucially by creating a new revision that matches the old one, never by erasing history. Helm keeps this record in the cluster itself, as Secrets, one per revision, which is how it knows what to diff on the next upgrade and what to restore on a rollback. helm uninstall removes the release and everything it created.
Real world: A chart is a mail-merge template. The letter body has blanks where a name and address go, and the values file is the spreadsheet that fills them. Each install is one printed, addressed copy; an upgrade reprints it with edits; a rollback pulls the previous version back out of the drawer. The template never changes, the spreadsheet decides what each copy says, and every copy is filed under a name so you can find and revise it later.
A named example shows the payoff. The community kube-prometheus-stack chart installs an entire monitoring platform — Prometheus, Alertmanager, Grafana and roughly thirty interlocking manifests — from a single helm install, tuned through values like retention, storage size and scrape interval. Nobody hand-writes those thirty files per environment; you install one versioned chart and set a handful of values. Public charts like it are indexed on Artifact Hub, and you add their repositories with helm repo add — but the mechanics are identical to the local chart you build today, which is the best way to learn them without depending on anyone else’s registry.
Today you scaffold your own chart with helm create, render it, install it as a release, override a value, upgrade, read the history, roll back, and uninstall — the full lifecycle on your local kind cluster.
Hands-On Lab
Budget about 25 minutes. This runs entirely on the local kind cluster from Day 67 — Kubernetes in Docker on WSL2, no cloud and ₹0. If Helm isn’t installed yet, grab the single binary from helm.sh (the get-helm-3 script, apt, or Homebrew). ReplicaSet hashes, pod suffixes, cluster IPs, ages and timestamps are unique to each run — yours will differ from the samples.
# 1. Confirm Helm 3 is installed. Helm 3 is a single binary — no Tiller, no cluster-side component.
helm version
# Output (your minor and patch versions and commit will differ — any Helm 3.x works):
# version.BuildInfo{Version:"v3.19.2", GitCommit:"…", GitTreeState:"clean", GoVersion:"go1.24.5"}
# 2. Scaffold a fresh, working chart. `helm create` writes an nginx-based
# Deployment, Service and Ingress template plus values.yaml under ./demo.
helm create demo
ls demo
# Output:
# Creating demo
# Chart.yaml charts templates values.yaml
# 3. Lint the chart before you install anything — catches structural mistakes early.
helm lint ./demo
# Output (the icon note is advisory, not a failure):
# ==> Linting ./demo
# [INFO] Chart.yaml: icon is recommended
#
# 1 chart(s) linted, 0 chart(s) failed
# 4. Render the chart locally WITHOUT touching the cluster — templates + values
# become plain Kubernetes YAML. Grepping the *quoted* image isolates the one
# Deployment line --set will change — the scaffold's test pod uses an unquoted
# `image: busybox`, which this pattern skips.
helm template demo ./demo | grep 'image: "'
# Output (values.yaml image.tag is "", so it falls back to Chart.yaml appVersion 1.16.0):
# image: "nginx:1.16.0"
# 5. Install the chart — this creates a RELEASE named demo (revision 1) on the cluster.
helm install demo ./demo
# Output (LAST DEPLOYED timestamp is yours; NOTES trimmed):
# NAME: demo
# LAST DEPLOYED: Sun Jul 12 10:24:31 2026
# NAMESPACE: default
# STATUS: deployed
# REVISION: 1
# NOTES:
# 1. Get the application URL by running these commands:
# export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=demo,app.kubernetes.io/instance=demo" -o jsonpath="{.items[0].metadata.name}")
# ...
# echo "Visit http://127.0.0.1:8080 to use your application"
# kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
# 6. List releases in this namespace — Helm tracks demo at revision 1.
helm list
# Output (UPDATED column trimmed):
# NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
# demo default 1 … deployed demo-0.1.0 1.16.0
# 7. See what the release actually created on the cluster.
kubectl get all
# Output (hashes, IPs and AGE differ):
# NAME READY STATUS RESTARTS AGE
# pod/demo-6b8f5c9d47-2xk9p 1/1 Running 0 25s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d
# service/demo ClusterIP 10.96.148.32 <none> 80/TCP 25s
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/demo 1/1 1 1 25s
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/demo-6b8f5c9d47 1 1 1 25s
# 8. Override a value and upgrade — --set replicaCount=3 scales the Deployment.
# This renders again and records revision 2.
helm upgrade demo ./demo --set replicaCount=3
kubectl get pods
# Output (upgrade banner trimmed; the ReplicaSet hash is unchanged — only replicas changed):
# Release "demo" has been upgraded. Happy Helming!
# ...
# REVISION: 2
# NAME READY STATUS RESTARTS AGE
# demo-6b8f5c9d47-2xk9p 1/1 Running 0 70s
# demo-6b8f5c9d47-8mtqr 1/1 Running 0 6s
# demo-6b8f5c9d47-lp4vz 1/1 Running 0 6s
# 9. Every install and upgrade is a revision. helm history shows the trail.
helm history demo
# Output (UPDATED trimmed):
# REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
# 1 … superseded demo-0.1.0 1.16.0 Install complete
# 2 … deployed demo-0.1.0 1.16.0 Upgrade complete
# 10. Roll back to revision 1 (replicaCount 1). This does NOT delete history —
# it creates a new revision that restores the old state.
helm rollback demo 1
kubectl get pods
# Output (scaled back to a single pod — the two newest are removed):
# Rollback was a success! Happy Helming!
# NAME READY STATUS RESTARTS AGE
# demo-6b8f5c9d47-2xk9p 1/1 Running 0 2m
# 11. The rollback itself is revision 3 — history is append-only, never rewritten.
helm history demo
# Output:
# REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
# 1 … superseded demo-0.1.0 1.16.0 Install complete
# 2 … superseded demo-0.1.0 1.16.0 Upgrade complete
# 3 … deployed demo-0.1.0 1.16.0 Rollback to 1
# 12. Remove the release and everything it created — the cluster is clean again.
helm uninstall demo
# Output:
# release "demo" uninstalled
Read the lifecycle back to yourself: one chart on disk, rendered to plain YAML, installed as a named release, upgraded with a single overridden value, rolled back, and removed — and Helm tracked every step as a revision it could diff and restore. That is the whole Helm loop you’ll repeat on real charts for the rest of your career.
Common Errors & Fixes
These three trip people up in their first hour with Helm. Read the error text slowly — parsing it is the actual skill.
Common error: Running
helm installwith only a release name and forgetting the chart path:Error: "helm install" requires 2 arguments Usage: helm install [NAME] [CHART] [flags]Why:
helm installneeds two things — the name to give the release and the chart to install. With one argument Helm can’t tell which you meant, so it refuses and prints the usage line. A common myth is thathelm install demo demo(no./) fails because Helm reads the baredemoas a repo chart — it doesn’t. Helm’s chart locator checks the local filesystem first (a plainos.Stat), so baredemoresolves to the./demodirectory and installs (or errorscannot re-use a name that is still in useif that release already exists). The./is a readability convention, not a requirement. To actually trigger the repo-vs-local path, install a name with no local match —helm install demo bitnami/nginxwithout adding that repo — which fails withfailed to download "bitnami/nginx".Fix: Pass both, with the path as an explicit local reference:
helm install demo ./demo. The./is what tells Helm “a chart on disk here,” versusrepo/chartfor a repository chart orchart-1.2.3.tgzfor a packaged one.How you’d spot it in prod: A CI step failing with “requires 2 arguments” usually means a variable expanded empty — the chart path or release name was blank. Echo the assembled
helm installcommand into the job log before it runs.
Common error: Re-running
helm installfor a release name that already exists:Error: INSTALLATION FAILED: cannot re-use a name that is still in useWhy: A release name is unique per namespace.
demois already installed, andhelm installonly creates — it will not adopt or overwrite an existing release — so the second install collides with the first.Fix: To change an existing release, use
helm upgrade demo ./demo(add--installto install-or-upgrade in one idempotent command — the standard move in CI). To start over,helm uninstall demofirst, then install. To run a second copy, install it under a different name.How you’d spot it in prod: A deploy pipeline that runs
helm installon every release works the first time and fails on the second with this message. Switching the step tohelm upgrade --installmakes it idempotent and the error disappears.
Common error: Using a colon instead of
=in a--setoverride (or otherwise leaving a key with no value):Error: failed parsing --set data: key "replicaCount:3" has no valueWhy:
--setexpectskey=value, dots for nesting (image.tag=1.27) and commas between pairs. A colon isn’t a separator here, so Helm reads the whole token as a key with nothing assigned and bails before it renders anything.Fix: Use
=:helm upgrade demo ./demo --set replicaCount=3. For several values, comma-separate them (--set replicaCount=3,image.tag=1.27) or, cleaner, keep them in a file and pass-f my-values.yaml.How you’d spot it in prod: The nastier cousin is silent —
--set replicas=3(wrong key) parses fine but changes nothing, because the chart’s key isreplicaCount. Helm never warns about an unused value, so confirm the result withhelm get values demoorkubectl get deploy, not the exit code.
Helm Interview Questions
Charts, values and the install → upgrade → rollback lifecycle are staple Phase-4 questions the moment Kubernetes is on your CV — a calm answer that names the release history beats reciting flags. The answer bank renders right after this note. Cover each answer, say your own version out loud first, then compare — recalling before revealing is what makes it stick for interview day.
Go Deeper
Optional extras if you have ~30 more minutes:
- 5 min — Re-install
demo, then runhelm get values demoandhelm get manifest demoto see exactly what values Helm stored and what YAML it applied — the release, made inspectable. - 10 min — Run
helm package ./demoto producedemo-0.1.0.tgz, then browse Artifact Hub — the central index where public charts live and where you’dhelm repo adda community chart like kube-prometheus-stack. The.tgzis all a chart repository actually serves. - 15 min — Read the concept of GitOps with Argo CD: git as the single source of truth, with a controller continuously reconciling the cluster to what’s committed. Instead of running
helm upgradeby hand, you commit the values change and Argo CD (or Flux) applies it — and it auto-corrects drift. Skim the Argo CD docs for how it renders and syncs Helm charts declaratively.
What is a Helm chart, and what problem does it solve? Both
A chart is a versioned package of templated Kubernetes manifests plus a values.yaml of defaults. Instead of hand-maintaining a dozen YAML files across dev, staging and prod, you template the parts that change and pass a values file per environment. helm install renders the templates against your values and applies the result. The problem it solves is repetition and drift: one parameterised package replaces copy-pasted manifests, and the same chart at the same version deploys identically everywhere — only the values differ. It's the apt or npm of Kubernetes: someone publishes a chart, you install it by name and tune it with values rather than editing raw manifests.
Walk me through install, upgrade and rollback in Helm. Both
helm install NAME CHART renders the chart and creates a release — a named, tracked instance recorded as revision 1. helm upgrade changes it: pass a newer chart or override values with --set or -f, and Helm renders again and applies the diff, bumping to revision 2. helm history shows every revision, and helm rollback NAME 1 returns to an earlier one — importantly it doesn't erase history, it creates a new revision that matches the old state. helm uninstall removes the release and its resources. Helm keeps this history in the cluster, as Secrets by default, which is how it knows what to diff and what to roll back to.
How does Helm turn templates and values into what runs on the cluster? Service
Templates in templates/ are Go text/template files with placeholders like {{ .Values.replicaCount }}. values.yaml supplies the defaults; --set and -f override them at install or upgrade time. Helm merges the values, renders every template into plain Kubernetes YAML, then sends that to the API server. You can see the rendered output without touching the cluster using helm template — it stops at the render step, which is ideal for reviewing a change or diffing it in CI. So the flow is always the same: templates plus merged values become rendered manifests, which get applied. Nothing magic reaches the cluster; it's ordinary YAML that Helm generated for you.
When would you use Helm over plain kubectl apply, and when not? Product
For anything with more than a couple of manifests that ships to multiple environments, Helm earns its keep: one chart, per-environment values, and a release history you can roll back. kubectl apply -f is fine for a single manifest or a quick experiment, but it has no notion of a release, no rollback, and no templating — you end up copy-pasting near-identical YAML. That said, Helm's templating can get gnarly, so some teams prefer Kustomize (overlays, no templating language) or a GitOps tool like Argo CD that syncs manifests from git. My rule: reach for Helm when packaging or consuming a reusable app; plain manifests or Kustomize when the config is small and static.
Mark Day 75 complete
Tomorrow you leave Kubernetes for infrastructure itself — Terraform's first apply, where a config file provisions real resources and one command makes them exist.
Stuck on today’s lab? Ask in Mission 90 Q&A