All Modules Why Helm Concepts Hands-on Lab Cheat Sheet

Helm

Package your Kubernetes manifests into one reusable chart — install, upgrade, and roll back like a pro.

Advanced A2 · Turn the A1 manifests into a templated chart with per-environment values.

Advanced Packaging ~60 min

What You'll Learn

  • Explain what Helm solves and the parts of a chart
  • Template manifests with values.yaml and {{ }} directives
  • Install, list, and inspect releases
  • Manage per-environment config with multiple values files
  • Upgrade and roll back releases safely
  • Render & debug locally with helm template / --dry-run, and pull charts from a repo

Prerequisites: A1 (Production Kubernetes) — we package the exact manifests you built there. A running k3d cluster.

Why Helm Exists

In A1 you ended up with a pile of raw YAML: db.yaml, web.yaml, hpa.yaml, rbac.yaml. Now imagine running that in dev, staging, and prod — each needs different replica counts, image tags, and resource sizes. With raw YAML you'd copy-paste and hand-edit each set. That's how environments drift apart and break.

Raw YAML doesn't scale

No variables, no reuse, no versioning of "what's deployed," and no one-command rollback. Change the image tag in prod and you're editing files by hand under pressure.

Helm is the package manager for Kubernetes. You template your manifests once, put the bits that change into values.yaml, and Helm renders + installs them as a versioned release you can upgrade or roll back instantly.

Think apt/npm, but for clusters

A chart is a package. helm install deploys it, helm upgrade ships a new version, helm rollback reverts — each tracked with a revision number. You can also pull ready-made charts (Postgres, Prometheus, Grafana) from public repos instead of writing YAML.

The Anatomy of a Chart

PieceWhat it is
ChartA folder of templated Kubernetes manifests + metadata = one package.
Chart.yamlMetadata: name, version, app version.
values.yamlDefault configuration — the knobs users can override.
templates/Your manifests with {{ }} placeholders filled from values.
ReleaseOne installed instance of a chart, with a revision history.
RepositoryA place to share charts (like Docker Hub for images).

The flow in one line

templates/ + values.yaml  →  helm install  →  rendered manifests applied as a release  →  helm upgrade / rollback manage revisions.

Hands-on Lab: Chart the Notes App

We'll turn the A1 manifests into a chart called notes-chart. Make sure your k3d cluster from A1 is running and the notes-app:1.0 image is imported.

1

Install Helm + scaffold a chart

curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash helm version helm create notes-chart # scaffolds a full example chart rm -rf notes-chart/templates/* # clear the boilerplate; we'll write our own

The scaffold leaves Chart.yaml, values.yaml, and an empty templates/ — exactly our starting point.

2

Define the knobs — notes-chart/values.yaml

Everything that differs between environments lives here:

web: replicas: 2 image: repository: notes-app tag: "1.0" pullPolicy: IfNotPresent databaseUrl: postgresql://notes:secret@db:5432/notesdb resources: requests: { cpu: 100m, memory: 128Mi } limits: { cpu: 500m, memory: 256Mi } db: storage: 1Gi password: secret
3

Template the web app — templates/web.yaml

This is the same Deployment + Service from A1, but the changing bits now read from .Values. Note {{ }} directives and the helpers toYaml/nindent:

apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-web spec: replicas: {{ .Values.web.replicas }} selector: matchLabels: { app: web } template: metadata: labels: { app: web } spec: containers: - name: web image: "{{ .Values.web.image.repository }}:{{ .Values.web.image.tag }}" imagePullPolicy: {{ .Values.web.image.pullPolicy }} env: - name: DATABASE_URL value: {{ .Values.web.databaseUrl | quote }} ports: - containerPort: 5000 resources: {{- toYaml .Values.web.resources | nindent 12 }} readinessProbe: httpGet: { path: /health, port: 5000 } --- apiVersion: v1 kind: Service metadata: name: web spec: selector: { app: web } ports: - port: 5000 targetPort: 5000

Three template tools you'll use constantly

{{ .Values.x }} inserts a value · {{ .Release.Name }} is a built-in (the release name) · {{- toYaml .Values.x | nindent N }} drops a whole YAML block in at the right indentation. The leading - trims preceding whitespace.

4

Template the database — templates/db.yaml

The StatefulSet, Secret, and headless Service from A1, with the password and storage size templated:

apiVersion: v1 kind: Secret metadata: { name: db-secret } stringData: POSTGRES_USER: notes POSTGRES_PASSWORD: {{ .Values.db.password | quote }} POSTGRES_DB: notesdb --- apiVersion: apps/v1 kind: StatefulSet metadata: { name: db } spec: serviceName: db replicas: 1 selector: matchLabels: { app: db } template: metadata: labels: { app: db } spec: containers: - name: postgres image: postgres:16 envFrom: - secretRef: { name: db-secret } ports: - containerPort: 5432 volumeMounts: - name: data mountPath: /var/lib/postgresql/data subPath: pgdata volumeClaimTemplates: - metadata: { name: data } spec: accessModes: ["ReadWriteOnce"] resources: requests: { storage: {{ .Values.db.storage }} } --- apiVersion: v1 kind: Service metadata: { name: db } spec: clusterIP: None selector: { app: db } ports: - port: 5432 targetPort: 5432
5

Render before you install — helm template

Always preview what Helm will generate. This catches template bugs without touching the cluster:

helm template demo ./notes-chart # print rendered YAML to your screen helm lint ./notes-chart # sanity-check the chart
6

Install the release

helm install notes ./notes-chart -n notes --create-namespace helm list -n notes # your release, revision 1 kubectl get pods -n notes

The "aha!" moment

One command deployed the entire stack — web, db, secret, services — as a single tracked release. No more juggling four kubectl apply commands.

7

Per-environment values

Create values-prod.yaml with only the overrides for production:

web: replicas: 5 image: { tag: "1.2" } resources: requests: { cpu: 250m, memory: 256Mi } limits: { cpu: "1", memory: 512Mi } db: storage: 10Gi

Layer it on top of the defaults with -f (later files win). Or override one value inline with --set:

helm upgrade notes ./notes-chart -n notes -f values-prod.yaml helm upgrade notes ./notes-chart -n notes --set web.replicas=8 # quick one-off

Same chart, many environments

One chart + values-dev.yaml / values-staging.yaml / values-prod.yaml = identical structure everywhere, with only the differences declared. This is how teams keep environments in sync.

8

Upgrade, history & rollback

Every install/upgrade creates a revision. If a release goes bad, revert in one command:

helm history notes -n notes # list revisions 1, 2, 3... helm rollback notes 1 -n notes # instantly revert to revision 1 helm status notes -n notes # current state

One-command safety net

No hunting for old YAML or remembering what changed — Helm stores every revision and rolls back atomically. This alone is why teams adopt it.

9

Bonus: use a chart from a repo

You rarely write charts for common software — you pull battle-tested ones. For example, a production-grade Postgres:

helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update helm search repo postgresql # helm install pg bitnami/postgresql -n notes # (would replace our hand-rolled db)

Clean up

helm uninstall notes -n notes removes the entire release in one go.

Helm Cheat Sheet

CommandWhat it does
helm create NAMEScaffold a new chart
helm lint ./chartCheck a chart for problems
helm template REL ./chartRender manifests locally (no cluster)
helm install REL ./chartDeploy a chart as a release
helm install REL ./chart -f vals.yamlInstall with custom values
helm install ... --set k=vOverride a single value inline
helm upgrade REL ./chartShip a new revision
helm upgrade --install REL ./chartInstall or upgrade (idempotent, great for CI)
helm history RELList revisions
helm rollback REL NRevert to revision N
helm list / helm status RELList releases / inspect one
helm uninstall RELRemove a release
helm repo add / updateAdd & refresh chart repositories

Troubleshooting

SymptomLikely cause & fix
nil pointer evaluating interfaceA value referenced in a template is missing from values.yaml — add it (or guard with default).
Wrong indentation in rendered YAMLUse nindent (not indent) after a newline, and mind the {{- whitespace trim.
cannot re-use a name that is still in useRelease already exists — helm upgrade instead of install, or uninstall first.
Values not taking effect-f file order matters (last wins); --set beats files. Confirm with helm template.
Upgrade left things brokenhelm rollback REL <previous> — that's what revisions are for.
Don't know what's deployedhelm get values REL and helm get manifest REL.

Your Challenge

  • Move the A1 HPA into the chart, with min/max replicas as values.
  • Add a values-dev.yaml (1 replica, tiny resources) and install a separate notes-dev release in its own namespace.
  • Use a _helpers.tpl named template for common labels and reuse it across manifests.
  • Gate the database behind {{- if .Values.db.enabled }} so it can be turned off.
  • Bonus: add the Bitnami Postgres chart as a dependency in Chart.yaml and helm dependency update.
# wrap the whole db.yaml content in: {{- if .Values.db.enabled }} # ...Secret, StatefulSet, Service... {{- end }} # and in values.yaml: db: enabled: true

Recap & What's Next

You can now

Package an app as a Helm chart, template it with values, install/upgrade/roll back releases, and manage many environments from one chart. Your A1 manifests are now a clean, versioned package.

Next up: A3 — GitOps with Argo CD, where Git becomes the single source of truth: commit a change to your chart's values and the cluster syncs itself — no more running helm upgrade by hand.

Helm

Objectives Why Helm Chart Anatomy Hands-on Lab Cheat Sheet Troubleshooting Challenge Recap