Skip to content

Phase 4 · ORCHESTRATION & IAC

Helm — charts, values, releases (Go Deeper: GitOps with Argo CD)

Day 75 of 90 ~55 min 0/20 in phase Builds on Day 74

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

Section 1 of 5 · ~3 min

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.

Helm renders a chart's templates and values.yaml into Kubernetes manifests, then helm install creates a named release whose revision number climbs with every upgrade and rollback. Chart templates/ + values.yaml Rendered manifests plain Kubernetes YAML Release: demo revision 1 → 2 → 3 helm template helm install helm upgrade and helm rollback bump the revision
Templates plus values render to manifests; helm install turns them into a named, versioned release.

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

Section 2 of 5 · ~4 min

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

Section 3 of 5 · ~3 min

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 install with only a release name and forgetting the chart path:

Error: "helm install" requires 2 arguments

Usage:  helm install [NAME] [CHART] [flags]

Why: helm install needs 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 that helm install demo demo (no ./) fails because Helm reads the bare demo as a repo chart — it doesn’t. Helm’s chart locator checks the local filesystem first (a plain os.Stat), so bare demo resolves to the ./demo directory and installs (or errors cannot re-use a name that is still in use if 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/nginx without adding that repo — which fails with failed 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,” versus repo/chart for a repository chart or chart-1.2.3.tgz for 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 install command into the job log before it runs.

Common error: Re-running helm install for a release name that already exists:

Error: INSTALLATION FAILED: cannot re-use a name that is still in use

Why: A release name is unique per namespace. demo is already installed, and helm install only 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 --install to install-or-upgrade in one idempotent command — the standard move in CI). To start over, helm uninstall demo first, 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 install on every release works the first time and fails on the second with this message. Switching the step to helm upgrade --install makes it idempotent and the error disappears.

Common error: Using a colon instead of = in a --set override (or otherwise leaving a key with no value):

Error: failed parsing --set data: key "replicaCount:3" has no value

Why: --set expects key=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 is replicaCount. Helm never warns about an unused value, so confirm the result with helm get values demo or kubectl get deploy, not the exit code.

Helm Interview Questions

Section 4 of 5 · ~1 min

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

Section 5 of 5 · ~1 min

Optional extras if you have ~30 more minutes:

  • 5 min — Re-install demo, then run helm get values demo and helm get manifest demo to see exactly what values Helm stored and what YAML it applied — the release, made inspectable.
  • 10 min — Run helm package ./demo to produce demo-0.1.0.tgz, then browse Artifact Hub — the central index where public charts live and where you’d helm repo add a community chart like kube-prometheus-stack. The .tgz is 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 upgrade by 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