43

Helm Charts

CKA prep • The Kubernetes package manager: charts, templates/values, releases, repos

Key terms

TermMeaning
HelmThe package manager for Kubernetes
ChartA versioned bundle of templated manifests
ValuesThe configuration that fills in the templates (values.yaml)
TemplateA manifest with Go template placeholders ({{ }})
ReleaseA named, installed instance of a chart in a cluster
RepositoryAn HTTP(S) index that serves packaged charts
RevisionEach install/upgrade bumps a release revision (for rollback)

Problem & solution

Real apps are many manifests — Deployment, Service, ConfigMap, Ingress, HPA — and you copy-paste them per environment, changing a few values each time. That is error-prone and unversioned. Helm packages all of it into one templated, versioned chart, parameterised by values, and tracks each install as a release you can upgrade and roll back.

Solution: Package manifests as a templated chart, override per-environment values at install time, and manage the lifecycle (install/upgrade/rollback) as versioned releases stored in-cluster.

The analogy

Buying furniture as a flat-pack kit means the parts and the assembly steps are standardized, you just supply one parameter sheet per room, the wood color here, five chairs there, and the same kit builds a different finished room each time. You order the kit from a furniture catalog, fill in the sheet, and end up with a specific assembled set in that room that you can later modify or take apart. Helm works the same way: a chart is the kit of templated manifests, values is the parameter sheet you fill per environment, a release is one installed, running instance, and a repository is the catalog you pull charts from.

Helm 3 architecture (no Tiller)

Helm 3 dropped the in-cluster Tiller server. The CLI renders client-side and talks to the API directly with your RBAC; release history lives in Secrets.

Chart directory structure

Every chart follows the same layout, so once you know it you can navigate any chart. Here is what each file and folder is for:

helm create mychart        # scaffold a working chart
helm lint mychart          # validate structure + templates
   mychart/
   ├── Chart.yaml          # name, version, appVersion, dependencies
   ├── values.yaml         # default configuration values
   ├── charts/             # vendored sub-chart dependencies
   ├── templates/          # templated manifests
   │   ├── deployment.yaml
   │   ├── service.yaml
   │   ├── _helpers.tpl    # reusable template snippets
   │   └── NOTES.txt       # post-install message
   └── .helmignore

Templates and values

Templates are manifests with Go template directives; .Values comes from values.yaml (and any override), .Release/.Chart are built-in objects.

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
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: {{ .Values.service.port }}
# values.yaml
replicaCount: 2
image:
  repository: nginx
  tag: "1.27"
service:
  port: 80

Render and inspect before applying

Always preview what a chart will produce before it touches the cluster. These commands render or server-validate without installing anything:

helm template mychart                       # render to stdout, apply nothing
helm template mychart --set replicaCount=5  # see an override applied
helm install web ./mychart --dry-run --debug  # server-validate without installing

Release lifecycle: install, upgrade, rollback

These are the everyday commands for the full life of a release, from first install through upgrades and rollback to removal:

# install a named release (override values inline or with a file)
helm install web ./mychart --set image.tag=1.27 -n prod --create-namespace
helm install web ./mychart -f prod-values.yaml -n prod

# list and inspect releases
helm list -n prod
helm status web -n prod
helm get values web -n prod

# upgrade (idempotent install+upgrade with one command)
helm upgrade --install web ./mychart --set replicaCount=4 -n prod

# history and rollback to a previous revision
helm history web -n prod
helm rollback web 1 -n prod

# remove the release (keep history with --keep-history)
helm uninstall web -n prod

Repositories and dependencies

Charts are shared through repositories, much like OS packages. These commands add a repo, find charts, and publish your own:

# add a public repo, refresh its index, search, and install
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo nginx
helm install store bitnami/nginx -n web --create-namespace

# package + push your own chart to a repo (or an OCI registry)
helm package mychart                       # -> mychart-0.1.0.tgz
helm push mychart-0.1.0.tgz oci://registry.example.com/charts

Declare chart dependencies in Chart.yaml and pull them with helm dependency update, which populates charts/.

End-to-end: from chart to running release

End-to-end example: scaffold, install, upgrade, and roll back a chart

A full lifecycle: scaffold a chart, trim it to a Deployment + Service, install revision 1, upgrade to revision 2 with --set, break it, then roll back to a known-good state and confirm the resulting revision history.

  1. Scaffold and lint a fresh chart:
helm create web
helm lint web
# expected: 1 chart(s) linted, 0 chart(s) failed
  1. Replace web/values.yaml defaults with a minimal, explicit set:
# web/values.yaml
replicaCount: 2
image:
  repository: nginx
  tag: "1.27"
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 80
  1. Point the generated web/templates/deployment.yaml container at those values (the key lines):
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: {{ .Values.service.port }}
  1. Preview the rendered YAML before touching the cluster:
helm template web ./web --set replicaCount=3 | grep -E "replicas|image:"
# expected: replicas: 3   and   image: "nginx:1.27"
  1. Install revision 1 into its own namespace:
helm install web ./web -n web --create-namespace
kubectl -n web get deploy web -o jsonpath='{.spec.replicas}{"\n"}'   # 2
helm list -n web
# expected: web   web   1   ...   deployed   web-0.1.0
  1. Upgrade to revision 2, scaling out and bumping the image:
helm upgrade --install web ./web -n web \
  --set replicaCount=4 --set image.tag=1.27.2
kubectl -n web get deploy web -o jsonpath='{.spec.replicas}{"\n"}'   # 4
helm history web -n web
# expected: revision 1 superseded, revision 2 deployed
  1. Push a bad upgrade (nonexistent tag), watch it fail to roll out, then roll back:
helm upgrade web ./web -n web --set image.tag=does-not-exist
kubectl -n web rollout status deploy/web --timeout=30s || true
# expected: pods stuck ImagePullBackOff

helm rollback web 2 -n web
kubectl -n web rollout status deploy/web
# expected: deployment "web" successfully rolled out
  1. Confirm the rollback created a NEW revision that restores revision 2's state:
helm history web -n web
# expected: revision 4 deployed, description "Rollback to 2"
kubectl -n web get deploy web -o jsonpath='{..image}{"\n"}'   # nginx:1.27.2

Common pitfalls

These are the common Helm mistakes and what each symptom usually means:

   - YAML indentation in templates  -> use nindent/indent, e.g. {{ toYaml .Values.x | nindent 8 }}
   - changed values, nothing moved  -> you ran `template`, not `upgrade`/`install`
   - release name collision         -> names are unique per namespace
   - secrets in values.yaml in git   -> keep secret values out of source control
   - rollback didn't help           -> rollback creates a NEW revision; check history
   - CRDs not upgraded              -> Helm does not upgrade CRDs from crds/ automatically

Key takeaways

  • A chart = templated manifests + default values; an install = a release.
  • Helm 3 is client-side (no Tiller); release state lives in Secrets.
  • helm template/--dry-run lets you preview rendered YAML safely.
  • helm upgrade --install is the idempotent workhorse; revisions enable rollback.
  • helm rollback makes a new revision that restores an old one.
  • Add repos (or OCI registries) to install and share charts.

Checklist

  • [ ] Scaffolded a chart with helm create and read the structure
  • [ ] Edited templates to consume .Values and rendered with helm template
  • [ ] Installed a release and overrode values with --set and -f
  • [ ] Upgraded the release and rolled it back to a prior revision
  • [ ] Added a repo, searched, and installed a third-party chart