Kubernetes · lesson 10 of 10
Intermediate
6Advanced
1Also part of Kubernetes
5- + Deploying FastAPI with Docker and Kubernetes: Images, Signals, and Probes from FastAPI
- + Deployment Strategies Compared: Rolling, Blue-Green, and Canary from DevOps
- + GitOps in Practice: Git as the Source of Truth from DevOps
- + Kubernetes Pod Security Admission: Enforcing the Restricted Profile from Cloud Security
- + CIS Kubernetes Benchmark Checks with kube-bench in kind from Cloud Security
Helm: Packaging Manifests, and a Real Rollback
Every earlier lesson hand-applies individual YAML files. Package the same Deployment and Service as a Helm chart, upgrade it with a real broken image tag, and roll it back with helm rollback.
By SkillByExample · Published
Every earlier lesson in this track ran kubectl apply -f against hand-written YAML files —
fine for a handful of manifests, but nothing tracks what changed between versions or lets you
undo a bad change in one command. Helm adds templating, versioned releases, and rollback on top
of the same underlying manifests.
A minimal chart
A chart is a directory with a fixed shape: Chart.yaml for metadata, values.yaml for
defaults, and templates/ for the manifests themselves, written with Go template syntax.
# mychart/Chart.yaml
apiVersion: v2
name: mychart
description: A minimal chart for the Helm lesson
type: application
version: 0.1.0
appVersion: "1.25"
# mychart/values.yaml
replicaCount: 2
image:
repository: nginx
tag: "1.25"
service:
port: 80
# mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-web
labels:
app: {{ .Release.Name }}-web
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}-web
template:
metadata:
labels:
app: {{ .Release.Name }}-web
spec:
containers:
- name: web
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 80
{{ .Release.Name }} is filled in at install time from the name you give the release, not the
chart — the same chart installed twice under different names produces two independently-named
sets of resources with no collision.
Rendering without touching the cluster
Before installing anything, helm template shows exactly what would be applied:
helm template demo mychart
---
# Source: mychart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-web
spec:
selector:
app: demo-web
ports:
- port: 80
targetPort: 80
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-web
labels:
app: demo-web
spec:
replicas: 2
selector:
matchLabels:
app: demo-web
template:
metadata:
labels:
app: demo-web
spec:
containers:
- name: web
image: "nginx:1.25"
ports:
- containerPort: 80
Every {{ .Release.Name }} became demo, and {{ .Values.replicaCount }} became the 2 from
values.yaml — this is plain, ordinary Kubernetes YAML underneath the templating.
Installing a release
helm install demo mychart
NAME: demo
LAST DEPLOYED: Wed Sep 16 13:49:27 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
kubectl get pods -l app=demo-web
helm list
NAME READY STATUS RESTARTS AGE
demo-web-5947d6558b-8bgmp 0/1 ContainerCreating 0 4s
demo-web-5947d6558b-qx9dh 0/1 ContainerCreating 0 4s
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
demo default 1 2026-09-16 13:49:27.500683 +0530 IST deployed mychart-0.1.0 1.25
helm list is the release-tracking kubectl apply -f never gave you — one row per install,
independent of how many raw Kubernetes objects the chart actually renders into.
Upgrading with —set, no file edits
helm upgrade demo mychart --set replicaCount=4
kubectl get pods -l app=demo-web
Release "demo" has been upgraded. Happy Helming!
NAME: demo
LAST DEPLOYED: Wed Sep 16 13:49:35 2026
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NAME READY STATUS RESTARTS AGE
demo-web-5947d6558b-8bgmp 1/1 Running 0 13s
demo-web-5947d6558b-npj5t 0/1 ContainerCreating 0 4s
demo-web-5947d6558b-qx9dh 1/1 Running 0 13s
demo-web-5947d6558b-xh6jh 0/1 ContainerCreating 0 4s
--set overrides one value from the command line without touching values.yaml — REVISION
moved from 1 to 2, and the pod count really did go from 2 to 4.
A real broken upgrade
helm upgrade demo mychart --set image.tag=does-not-exist-xyz
kubectl get pods -l app=demo-web
Release "demo" has been upgraded. Happy Helming!
NAME: demo
LAST DEPLOYED: Wed Sep 16 13:49:44 2026
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None
NAME READY STATUS RESTARTS AGE
demo-web-5947d6558b-8bgmp 1/1 Running 0 22s
demo-web-5947d6558b-qx9dh 1/1 Running 0 22s
demo-web-68489df84f-m9ld8 0/1 ErrImagePull 0 5s
Helm reports STATUS: deployed — it applied the manifests successfully, which is all it
checks by default. But kubectl get pods shows the real consequence: the new pod can’t pull
nginx:does-not-exist-xyz at all. The two old, working pods from revision 2 are still serving
traffic; only the newly-created replica is broken, because this Deployment’s rolling-update
strategy replaces pods gradually rather than all at once.
Rolling back
helm history demo
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Wed Sep 16 13:49:27 2026 superseded mychart-0.1.0 1.25 Install complete
2 Wed Sep 16 13:49:35 2026 superseded mychart-0.1.0 1.25 Upgrade complete
3 Wed Sep 16 13:49:44 2026 deployed mychart-0.1.0 1.25 Upgrade complete
helm rollback demo 2
kubectl get pods -l app=demo-web
helm history demo
Rollback was a success! Happy Helming!
NAME READY STATUS RESTARTS AGE
demo-web-5947d6558b-8bgmp 1/1 Running 0 34s
demo-web-5947d6558b-8j26l 1/1 Running 0 4s
demo-web-5947d6558b-ddsdf 1/1 Running 0 4s
demo-web-5947d6558b-qx9dh 1/1 Running 0 34s
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Wed Sep 16 13:49:27 2026 superseded mychart-0.1.0 1.25 Install complete
2 Wed Sep 16 13:49:35 2026 superseded mychart-0.1.0 1.25 Upgrade complete
3 Wed Sep 16 13:49:44 2026 superseded mychart-0.1.0 1.25 Upgrade complete
4 Wed Sep 16 13:49:57 2026 deployed mychart-0.1.0 1.25 Rollback to 2
helm rollback 2 doesn’t reverse revision 3 in place — it creates a brand-new revision 4 whose
manifests are identical to revision 2’s. History keeps growing forward; nothing is deleted. All
4 pods are Running again on the good nginx:1.25 image, and the broken ErrImagePull pod is
gone.
Practice
1. Run helm get values demo after the upgrades above. What does it show?
helm get values demo
USER-SUPPLIED VALUES:
replicaCount: 4
Only replicaCount: 4 — not image.tag. Revision 2 (the rollback’s target) only ever had
--set replicaCount=4 applied to it; the broken image.tag=does-not-exist-xyz override
belongs to revision 3, which this release rolled back past. helm get values shows exactly
what was overridden at the currently-active revision, not a running log of every override ever
set — image.tag isn’t there because at revision 2 it was still the chart’s plain default,
never a user override.
2. Uninstall the release. Does the chart directory on disk disappear too?
helm uninstall demo
release "demo" uninstalled
No — mychart/ on disk is untouched; helm uninstall only removes the release (the
Kubernetes resources it created, plus Helm’s own release history for that name). The chart
itself is just a template you can helm install again under the same or a different name.
3. Try helm rollback demo 99 — a revision number that was never created.
helm rollback demo 99
Error: release has no 99 version
A specific, named error rather than a generic failure — Helm distinguishes “this release
exists but that revision doesn’t” from “this release doesn’t exist at all” (the error Practice
2’s helm rollback would produce after the uninstall, since by then there’s no release left to
have any revision).
4. Package the chart with helm package instead of installing it. What does it produce?
helm package mychart
Successfully packaged chart and saved it to: /path/to/mychart-0.1.0.tgz
A single versioned .tgz archive at the full path shown — Chart.yaml’s version field names
the file. This is the distributable unit for a chart repository: helm install and helm upgrade both accept a .tgz path or repo reference exactly like they accept a local
directory.
5. Add a second value to values.yaml that nothing in templates/ references, then run helm template. Does Helm complain?
An unused value (e.g. adding unusedKey: hello to values.yaml) produces no warning or error
from helm template — Helm only fails on a value template references but can’t find (an
undefined key), never on an extra one sitting unused. Values files commonly accumulate settings
for optional features a given chart’s templates don’t all consume.