06 · Helm Basics (Charts & Values)¶
Not run against a live cluster
Commands and output below follow documented Helm v3 behavior; not executed against a live cluster in this session.
The problem: raw YAML doesn't parameterize or version well¶
A real app is often a dozen+ manifests (Deployment, Service, Ingress, ConfigMap, PVC...) that need slightly different values per environment (dev vs. prod replica counts, image tags, hostnames). Copy-pasting and hand-editing YAML per environment doesn't scale and has no versioning or rollback story of its own. Helm packages a set of manifests as a templated, versioned unit called a chart.
Chart anatomy¶
mychart/
├── Chart.yaml # name, version, description
├── values.yaml # default configuration values
└── templates/
├── deployment.yaml
├── service.yaml
└── _helpers.tpl # reusable template snippets
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-mychart
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080
Templates use Go's text/template syntax; .Values comes from
values.yaml (overridable per install), .Release carries install-time
metadata (name, namespace, revision), .Chart carries Chart.yaml
fields.
Installing, upgrading, rolling back¶
helm install shop ./mychart
# NAME: shop
# STATUS: deployed
# REVISION: 1
helm upgrade shop ./mychart --set image.tag=1.5.0
# REVISION: 2
helm history shop
# REVISION STATUS DESCRIPTION
# 1 superseded Install complete
# 2 deployed Upgrade complete
helm rollback shop 1
# REVISION: 3 (a new revision recording "went back to revision 1's config")
Every helm install/upgrade/rollback creates a new numbered
release revision — rollback isn't a special undo mechanism, it's just
another upgrade whose target manifest happens to be an old revision's.
Overriding values¶
helm install shop ./mychart --set replicaCount=5 --set image.tag=1.5.0
helm install shop ./mychart -f values-prod.yaml
-f merges on top of the chart's own values.yaml; --set merges on top
of that — later sources win field-by-field, not whole-file replacement.
Inspecting what a chart will actually produce¶
helm template shop ./mychart --set replicaCount=5
# renders final YAML to stdout, no cluster contact -- great for reviewing/diffing before applying
helm install shop ./mychart --dry-run --debug
# renders AND validates against the connected cluster's API without creating anything
Public charts¶
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm show values bitnami/postgresql | less
helm install my-db bitnami/postgresql --set auth.postgresPassword=changeme
Worked example: chart install, upgrade, rollback¶
helm install shop ./mychart --set replicaCount=3
kubectl get deploy shop-mychart
# 3/3 ready
helm upgrade shop ./mychart --set replicaCount=3,image.tag=1.5.0-broken
kubectl get pods -l app=shop
# CrashLoopBackOff
helm rollback shop 1
kubectl get deploy shop-mychart -o jsonpath='{.spec.template.spec.containers[0].image}'
# myapp:1.4.0 -- back to the known-good release
How It Actually Works¶
- Helm has no in-cluster server component (since v3) — it's a client
that renders templates locally and applies plain manifests via the
Kubernetes API.
helm installruns the Go template engine againsttemplates/*.yamlwith.Values/.Release/.Chartin scope, produces final YAML, and submits it the same waykubectl applywould — Helm adds no controller, no CRD reconciliation, no admission webhook by default. - A release's state is itself stored as a Kubernetes Secret in the
release's namespace. Each revision is persisted as a Secret (type
helm.sh/release.v1, name likesh.helm.release.v1.shop.v2) whose data includes the fully rendered manifest and the values used —helm history/rollbackwork by reading these Secrets back, not by asking the live objects what they currently look like, which is why deleting these Secrets by hand (or a namespace) silently destroys Helm's memory of a release even if the underlying Deployment/Service objects still exist. - Upgrade computes a three-way diff, not a blind re-apply.
helm upgradediffs the previous rendered manifest (from the stored release Secret), the new rendered manifest, and the live cluster state, then issues a strategic merge patch — this is how Helm detects and removes resources that existed in the old release but were deleted from the new chart version (a plain re-applyof only the new templates would leave orphans behind). - Rollback is possible purely because past fully-rendered manifests are
retained, not because Kubernetes tracks "chart history" natively.
helm rollback shop 1re-reads revision 1's stored Secret, takes its already-rendered manifest verbatim, and applies it as a new revision — this is why a chart'stemplates/directory orvalues.yamlchanging later has zero effect on what a rollback to an old revision produces.
Exercise¶
Write a minimal chart with Chart.yaml, values.yaml (image repo/tag,
replica count), and a templates/deployment.yaml that uses both values.
Run helm template to inspect the rendered output before installing,
then helm install, helm upgrade --set image.tag=<bad-tag>, and helm
rollback back to revision 1 — check helm history after each step to see
the revision count grow.