Anurag's Notes

Helm Basics (2025 Updates)

← 12 Troubleshooting · Index · 14 Kustomize Basics (2025 Updates) →

Contents


13.1 Helm Introduction

Source: KodeKloud notes

The problem: an "app" is many objects

  • Kubernetes knows individual objects, not "the WordPress app". A WordPress site needs, coordinated together:
ResourceRole in the appTip
Deploymentruns Pods (MySQL, web server)Service selector must match Pod template labels
PersistentVolume / PVCDB / file storagePVC requests storage, PV provides it (§8.7, §8.8)
Servicenetwork access + load-balancingselector ↔ Pod labels; Service port ↔ container port
Secretadmin password, API keysdata: values must be base64 (echo -n 'pw' | base64) (§5.10)
Job / CronJobbackups, migrations, maintenance—
  • Manual approach = one YAML per object + kubectl apply -f each. Pain points:
    • later changes (storage size, image, labels, credentials) mean editing and re-applying many files;
    • deleting the app means remembering every object that belongs to it;
    • one big multi-document file (--- separated) centralises things but is hard to search/edit; you still micromanage each resource.
# wordpress-app.yaml - the manual way (multi-document); kubectl apply -f wordpress-app.yaml
apiVersion: v1
kind: Secret
metadata:
  name: wordpress-admin-password
type: Opaque
data:
  password: cGFzc3dvcmQ=          # base64 of "password"
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  type: LoadBalancer
  ports:
    - port: 80
  selector:                       # must match Pod labels
    app: wordpress
    tier: frontend
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 20Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  hostPath:                       # a volume source is required (hostPath, nfs, csi, ...)
    path: /mnt/data
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:5.6        # add env, volumeMounts, ports as needed

[!CAUTION] Correction: the original Secret uses password: CalksdIkeBGmxcv23kjsdlkjr== (27 chars, not valid base64 → the API server rejects it) and its PV has no volume source (not applyable). Replaced with a valid base64 value and a hostPath source above.

What Helm solves

  • Helm = package manager for Kubernetes. Treats the app as one package (chart) of related manifests; installs/upgrades/uninstalls them as one unit (release).
  • Benefits:
    • package many manifests as a chart;
    • centralise configuration in values.yaml;
    • install / upgrade / rollback / uninstall with single commands;
    • tracks release revisions → safe rollbacks;
    • reuse the same chart across dev/stage/prod by supplying different values files.
  • Analogy: a desktop installer vs copying files by hand, plus versioning and lifecycle management.
# values.yaml - the single place to configure the app
wordpressUsername: user
# wordpressPassword: if not set, defaults to a random 10-char alphanumeric string
wordpressEmail: user@example.com
wordpressFirstName: FirstName
wordpressLastName: LastName
wordpressBlogName: "User's Blog!"
ActionCommandEffect
Installhelm install <release> <chart> --values values.yamlcreates release (revision 1)
Upgradehelm upgrade <release> <chart> --values values.yamlapplies new values/chart, new revision
Roll backhelm rollback <release> <revision>reverts to an earlier revision
Uninstallhelm uninstall <release>deletes all objects of the release

[!WARNING] Exam traps / remember: Helm knows which objects belong to a release, so helm uninstall removes the release’s managed resources; kept resources, CRDs and persistent data can remain (see §16.2). install / upgrade / template take <release-name> <chart> (release first); rollback takes <release> <revision>.

13.2 Installation and Configuration

Source: KodeKloud notes

Prerequisites

  • Running cluster (local or remote) + kubectl installed and configured; Helm uses the same kubeconfig / current context → it targets whatever cluster kubectl targets.
  • Verify first: kubectl get nodes must work. Fix kubeconfig/cluster access before blaming Helm.
  • Platforms: Linux, macOS, Windows.

Install methods

MethodUse caseNotes
SnapUbuntu / distros with snapdneeds --classic so Helm can read ~/.kube/config
APTDebian/Ubuntu, signed managed packageadd Helm signing key + repo (recommended)
pkgFreeBSD / pkg-basedsystem package manager
HomebrewmacOSbrew install helm
Chocolatey / binariesWindowsbinaries at github.com/helm/helm/releases
# Snap
sudo snap install helm --classic        # --classic = no strict sandbox, can read ~/.kube/config

# Debian/Ubuntu (APT, signed keyring) - as shown in the course
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates gnupg
curl https://baltocdn.com/helm/signing.asc | sudo gpg --dearmor -o /usr/share/keyrings/helm.gpg
echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" \
  | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list > /dev/null
sudo apt-get update
sudo apt-get install -y helm

# FreeBSD
sudo pkg install helm

[!NOTE] Beyond the course: in 2025 the Helm project moved its Debian/Ubuntu APT repo off baltocdn.com to Buildkite. If the course URL fails, use the current instructions at helm.sh/docs/intro/install, e.g.:

curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update && sudo apt-get install -y helm

Distro-agnostic alternative: the official script curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash.

Verify

helm version                       # Helm installed?
helm list --all-namespaces         # (-A) can Helm reach the cluster and list releases?
helm repo add stable https://charts.helm.sh/stable   # optional: add a repo
helm repo update                   # refresh repo index cache
helm repo list                     # show configured repos

Troubleshooting

  • Auth/connectivity errors from helm → check kubectl first: kubectl config current-context, kubectl get nodes.
  • Snap: drop --classic only if you deliberately want strict sandboxing and have arranged other kubeconfig access.

[!CAUTION] Correction: the course says helm version shows "client and server versions". That was Helm 2 (Tiller = server). Helm 3 has no server component; helm version prints only the client build (version.BuildInfo{Version:"v3.x.y", ...}) and does not contact the cluster — use helm list -A to prove cluster access.

[!NOTE] Beyond the course: the stable repo (charts.helm.sh/stable) is archived/deprecated since Nov 2020 (no updates). Find maintained charts on Artifact Hub (§13.4, §13.6).

13.3 A quick note about Helm2 vs Helm3

Source: KodeKloud notes

History

  • Helm 1.0 — Feb 2016 · Helm 2.0 — Nov 2016 · Helm 3.0 — Nov 2019.
  • Helm 1/2 predated mature Kubernetes RBAC and CRDs, so Helm implemented things itself (server-side Tiller). Once Kubernetes had those primitives, Helm 3 relied on the platform instead.

Tiller removed

  • Helm 2: CLI → Tiller (in-cluster server) → kube-apiserver. Tiller acted with broad ("god-mode") privileges; release history stored by Tiller.
  • Helm 3: CLI talks directly to kube-apiserver with the caller's kubeconfig credentials → Kubernetes RBAC applies to the user/ServiceAccount running helm, exactly like kubectl.

Helm 2 Tiller architecture vs Helm 3 direct API access, and the three inputs of Helm 3's three-way merge Diagram (new): Helm 2 vs Helm 3 architecture and the Helm 3 three-way merge inputs.

Revisions recap

  • helm install → revision 1 (snapshot of release state); each helm upgrade → new revision; helm rollback → another new revision that reverts to an earlier state.

Helm 2: two-way diff (surprising rollbacks)

  • Compared only the stored previous manifest vs the newly rendered chart (chart + values).
  • Live edits made outside Helm (e.g. kubectl set image) were never in Tiller's stored manifests → rollback could miss reverting them; upgrades could silently overwrite/lose them.
helm install wordpress ./wordpress-chart       # rev 1: image wordpress:4.8-apache
helm upgrade wordpress ./wordpress-chart-v2    # rev 2
kubectl set image deployment/wordpress wordpress=wordpress:5.8-apache   # manual drift BEFORE rollback
helm rollback wordpress 1                      # rev 3; Helm 2 does not compare live drift

Helm 3: three-way strategic merge patch

  • Inputs: (1) manifests of the target revision, (2) manifests of the currently deployed revision (last stored), (3) live state of the objects in the cluster.
  • Example: install rev 1 (wordpress:4.8-apache) → kubectl set image … wordpress:5.8-apache → helm rollback wordpress 1. Helm 3 sees live = 5.8 vs target = 4.8 and patches the image back to 4.8-apache.
  • Effects: drift is detected and reverted where needed; intentional live edits can be preserved during upgrades; more predictable upgrades/rollbacks where operators sometimes edit directly.
FeatureHelm 2Helm 3
Server-side componentTiller in clusternone (client only)
Auth modelTiller's privileges (Tiller RBAC)caller's credentials; K8s RBAC on the caller
Upgrade/rollback diff2-way (stored manifest vs rendered chart)3-way (target rev, current rev, live state)
Manual live changesoften lost/ignoredconsidered in the merge
CRD / RBACimplemented some features itselfuses Kubernetes-native CRDs + RBAC

[!WARNING] Exam traps / remember: No Tiller in Helm 3 — no helm init, nothing to install in the cluster. If helm install gets forbidden, it is your RBAC that is missing, not Helm's.

[!NOTE] Beyond the course: Helm 2-only commands/flags you may still see in old docs: helm init (installed Tiller), helm delete --purge (→ Helm 3 helm uninstall), --name <release> (→ positional release name). Helm 4 went GA in late 2025; it is also Tiller-less and the basic commands in this section are unchanged — check helm version in your environment.

13.4 Helm Components

Source: KodeKloud notes

Components

ComponentWhat it isExample
Helm CLIlocal tool performing Helm actionshelm install, helm upgrade, helm rollback
Chartfile tree: templates + values.yaml + Chart.yaml describing an app's resources./mychart, bitnami/wordpress
Releaseone installed instance of a chart in a clusterhelm install my-site bitnami/wordpress
Revisionsnapshot of a release's state; new one per Helm changecreated by helm upgrade / rollback
Repositorypublic/private hosting of chartsArtifact Hub, custom chart repo
Storage backendwhere release metadata lives in-clusterSecrets (default) or ConfigMaps

Helm components: chart pulled from an online repository by the helm CLI; releases with revisions stored in-cluster as a Secret

  • Release metadata stored in the cluster (Helm 3 default: Secrets in the release's namespace) → anyone with access to that namespace/cluster can list releases, see history and roll back; no local state to share.

[!NOTE] Beyond the course: each revision is one Secret of type helm.sh/release.v1 named sh.helm.release.v1.<release>.v<revision> (kubectl get secrets -n <ns> -l owner=helm). Switch backend with HELM_DRIVER=configmap (also secret, memory, sql). Deleting those Secrets makes Helm "forget" the release.

Charts and templating

  • Templates = YAML with Go-template placeholders; values.yaml supplies inputs. Most users never touch templates — they override values (values.yaml, --values, --set).
  • Common patterns:
    • .Values.x — values from values.yaml + CLI overrides;
    • include + helper templates — shared logic, consistent names/labels;
    • if / with / range — conditional / repeated rendering.
# templates/service.yaml + templates/deployment.yaml (combined view)
apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: http          # named container port
      protocol: TCP
      name: http
  selector:
    app: hello-world
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: {{ .Values.replicaCount }}             # from values.yaml
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: nginx
          image: "{{ .Values.image.repository }}"  # from values.yaml
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
# values.yaml
replicaCount: 1
image:
  repository: nginx

Advanced patterns in big charts (WordPress, Prometheus, Redis) — helpers, .Release.* objects, pipelines, whitespace control:

apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
kind: Deployment
metadata:
  name: {{ include "common.names.fullname" . }}
  namespace: {{ .Release.Namespace | quote }}
  labels:
{{- include "common.labels.standard" . | nindent 4 }}
{{- if .Values.commonLabels }}
{{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
{{- end }}
{{- if .Values.commonAnnotations }}
  annotations:
{{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
{{- end }}
spec:
  selector:
    matchLabels: {{- include "common.labels.matchlabels" . | nindent 6 }}
{{- if .Values.updateStrategy }}
  strategy: {{- toYaml .Values.updateStrategy | nindent 4 }}
{{- end }}
{{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}      # only rendered when HPA is disabled
{{- end }}
  • {{- trims preceding whitespace; nindent N = newline + indent N; quote wraps in quotes; toYaml dumps a values block; dict builds a map.

Releases

  • Same chart → many independent releases (e.g. dev and prod), each with its own revision history (rollback/audit):
helm install my-site bitnami/wordpress          # release 1
helm install my-second-site bitnami/wordpress   # independent release from same chart

Finding charts: Artifact Hub

  • artifacthub.io aggregates many publishers/repos (Bitnami, AppsCode, community, vendors). Use it to search (Redis, NGINX, WordPress, Prometheus), compare versions, see publisher metadata and Official / Verified Publisher badges (plus security rating), and copy install commands and values examples.

[!WARNING] Exam traps / remember: Only Helm commands create revisions. kubectl scale/edit/set image on a Helm-managed object is not recorded in helm history. Release ≠ chart: one chart can have many releases; release names are unique per namespace.

  • Artifact Hub is not Helm-only. Filter by Kind = Helm charts (it also lists OLM operators, Krew plugins, OPA policies, Falco rules, Tekton tasks…) and tick the Official / Verified publishers filters. Each card shows VERSION (chart) and APP VERSION (app).

[!CAUTION] Correction: fixed the course template’s metadata indentation: commonLabels needs nindent 4 under metadata.labels; annotations must be under metadata, not a top-level field.

13.5 Helm charts

Source: KodeKloud notes

What a chart is

  • You declare the end state (e.g. "3 replicas of nginx"); Helm renders templates and makes the API calls to get there.
  • Chart = instruction set to produce manifests: templates (one or more) + values.yaml (defaults) + Chart.yaml (metadata).
  • Templates read {{ .Values.… }} from values.yaml or CLI-supplied values (same hello-world Service/Deployment + values.yaml as §13.4).
helm install hello-world ./hello-world-chart     # install from a local chart directory

Directory layout

hello-world-chart/
  Chart.yaml      # metadata (required)
  values.yaml     # configurable defaults
  templates/      # template files rendered with values
  charts/         # dependency charts (optional)
  README.md       # docs (optional)
  LICENSE         # licence (optional)

Chart.yaml

apiVersion: v2                    # CHART API version (v2 = Helm 3), NOT a K8s apiVersion
appVersion: 5.8.1                 # version of the app deployed (informational)
version: 12.1.27                  # version of the chart itself
name: wordpress
description: Web publishing platform for building blogs and websites.
type: application                 # or "library"
dependencies:
  - condition: mariadb.enabled    # dependency only rendered if this value is true
    name: mariadb
    repository: https://charts.bitnami.com/bitnami
    version: 9.x.x
keywords:
  - application
  - blog
  - wordpress
maintainers:
  - email: containers@bitnami.com
    name: Bitnami
home: https://github.com/bitnami/charts/tree/master/bitnami/wordpress
icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png
FieldMeaning
apiVersionchart API version; v2 for Helm 3 (enables dependencies in Chart.yaml, library charts)
namechart name
versionchart version, managed by chart maintainer, independent of app
appVersionversion of the application (e.g. WordPress 5.8.1)
descriptionone-line description
typeapplication (deployable) or library (helpers only, not installable)
dependenciesother charts pulled from repos, optional condition toggle
keywords, maintainers, home, iconoptional discovery/attribution metadata

Dependencies

  • Compose charts from pre-built ones (WordPress + MariaDB) via dependencies:; condition enables/disables each.
helm dependency update ./hello-world-chart    # download/refresh deps into charts/

Best practices

  • Per-environment values in values files: helm upgrade --install <release> <chart> -f production-values.yaml (or --set for a few keys).
  • Render locally without installing: helm template <release> <chart>; validate against the API server: helm template <release> <chart> | kubectl apply --dry-run=server -f -.
  • Version the chart (version) independently of the app (appVersion).

[!WARNING] Exam traps / remember: version = chart version (what --version selects); appVersion = app version (the APP VERSION column). Chart apiVersion: v2 ≠ Kubernetes apiVersion.

[!NOTE] Beyond the course: Chart.yaml required fields are apiVersion, name, version. Helm 2 charts used apiVersion: v1 with dependencies in a separate requirements.yaml. helm create <name> scaffolds a chart (adds templates/_helpers.tpl, templates/NOTES.txt, .helmignore); helm lint <dir> checks it; helm package <dir> builds the .tgz.

13.6 Working with Helm basics

Source: KodeKloud notes

Use built-in help

  • helm / helm --help lists common actions: search (find charts), pull (download chart locally to inspect), install, list; and commands incl. completion, create, dependency, env, get, help, history, …
  • Help settles exact names in the exam, e.g. undo is helm rollback (there is no helm restore).
  • helm repo --help subcommands: add, index (generate index file for a dir of packaged charts), list, remove, update.
  • helm search needs a subcommand: hub (Artifact Hub or your own hub instance) or repo (repos you added locally).
ActionCommand
Search Artifact Hubhelm search hub <keyword>
Search added reposhelm search repo <keyword>
Add repohelm repo add bitnami https://charts.bitnami.com/bitnami
Refresh repo cachehelm repo update (like apt-get update)
List / remove reposhelm repo list / helm repo remove <name>
Installhelm install my-release bitnami/wordpress
List releaseshelm list
Uninstallhelm uninstall my-release (removes all its K8s objects)

Find → add repo → install → list → uninstall

helm search hub wordpress
# URL                                                 CHART VERSION  APP VERSION  DESCRIPTION
# https://artifacthub.io/packages/helm/bitnami/wo...  12.1.27        5.8.1        Web publishing platform ...
#   APP VERSION = upstream app version (WordPress); CHART VERSION = chart's own version

helm repo add bitnami https://charts.bitnami.com/bitnami
# "bitnami" has been added to your repositories

helm install my-release bitnami/wordpress
# NAME: my-release  NAMESPACE: default  STATUS: deployed  REVISION: 1
# NOTES: CHART NAME: wordpress  CHART VERSION: 12.1.27  APP VERSION: 5.8.1
#   my-release-wordpress.default.svc.cluster.local (port 80)

helm list
# NAME        NAMESPACE  REVISION  UPDATED  STATUS    CHART              APP VERSION
# my-release  default    1         ...      deployed  wordpress-12.1.27  5.8.1

helm uninstall my-release
# release "my-release" uninstalled
  • Artifact Hub chart page = exact install commands + repo URL, components deployed, configurable values. Prefer Official / Verified Publisher charts.
  • Post-install NOTES are written by the chart author (rendered from templates/NOTES.txt): how to access the app, default credentials, next steps.

[!WARNING] Exam traps / remember: helm search repo only searches repos you have added (run helm repo update if results look stale); helm search hub queries Artifact Hub (needs internet). Install references the chart as <repo-name>/<chart> — the repo name is whatever you chose in helm repo add.

[!NOTE] Beyond the course — exam-useful flags:

helm install web bitnami/nginx -n apps --create-namespace   # release in a namespace (created if missing)
helm list -A                                                 # releases in all namespaces (default: current ns only)
helm list -n apps -a                                         # -a/--all: all states; default shows deployed + failed
helm search repo nginx --versions                            # all chart versions in added repos
helm status web -n apps                                      # status + NOTES again
helm get values web -n apps                                  # user-supplied values (--all for merged)
helm get manifest web -n apps                                # rendered manifests of the current revision
helm uninstall web -n apps                                   # -n required if not in current namespace

Bitnami now publishes charts primarily as OCI artifacts (e.g. helm install my-release oci://registry-1.docker.io/bitnamicharts/wordpress) and in 2025 restricted its free image catalogue; older chart/image versions used in the course may no longer resolve in a fresh lab.

  • helm search hub <kw> lists the same app from several publishers (e.g. riftbit/… 12.1.16, bitnami-aks/… 12.1.18, bitnami/wordpress 12.1.27, all APP VERSION 5.8.1). Choose by publisher (Official / Verified badge), not by the first row.

13.7 Customizing chart parameters

Source: KodeKloud notes

Where defaults come from

  • Chart's values.yaml exposes fields; templates inject them into manifests (e.g. as container env vars).
# values.yaml (bitnami/wordpress fragment)
wordpressUsername: user
wordpressPassword: ""
existingSecret: ""
wordpressEmail: user@example.com
wordpressFirstName: ""
wordpressBlogName: "User's Blog!"
# templates/deployment.yaml (fragment) - how values reach the Pod
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: wordpress
          image: {{ template "wordpress.image" . }}
          env:
            - name: WORDPRESS_DATABASE_NAME
              value: {{ include "wordpress.databaseName" . | quote }}
            - name: WORDPRESS_DATABASE_USER
              value: {{ include "wordpress.databaseUser" . | quote }}
            - name: WORDPRESS_USERNAME
              value: {{ .Values.wordpressUsername | quote }}
            - name: WORDPRESS_PASSWORD            # password comes from a Secret, not plain env
              valueFrom:
                secretKeyRef:
                  name: {{ include "wordpress.secretName" . }}
                  key: wordpress-password
            - name: WORDPRESS_BLOG_NAME
              value: {{ .Values.wordpressBlogName | quote }}

Precedence

  • Chart values.yaml (defaults) < --values/-f files (multiple allowed; later file wins) < --set / --set-file (highest).

Helm values precedence: chart values.yaml lowest, then -f files in order, then --set/--set-file highest, merged into .Values and rendered Diagram (new): values layers merged into .Values, highest precedence on top.

1. --set (quick one-off overrides)

helm install my-release bitnami/wordpress \
  --set wordpressBlogName="Helm Tutorials" \
  --set wordpressEmail="john@example.com"
  • Quote values with spaces/special chars; nested keys with dots: --set persistence.enabled=true; complex values / long lists → use a file.
  • Don't put secrets in --set (shell history). Use Kubernetes Secrets (e.g. chart's existingSecret), --set-file key=path, or protected values files.

2. Custom values file (many / repeatable / version-controlled changes)

# custom-values.yaml - only the keys you override
wordpressBlogName: "Helm Tutorials"
wordpressEmail: "john@example.com"
helm install my-release bitnami/wordpress --values custom-values.yaml   # -f is the short form

3. Pull and edit the chart locally

helm pull bitnami/wordpress            # downloads wordpress-<version>.tgz
helm pull bitnami/wordpress --untar    # download + extract → ./wordpress/
ls wordpress                           # Chart.yaml values.yaml templates/ charts/ ...
# edit wordpress/values.yaml (or templates)
helm install my-release ./wordpress    # local dir's values.yaml is the default; -f/--set still override
MethodUse whenExample
Chart defaultsdefaults are finehelm install my-release bitnami/wordpress
--setone or a few changes--set wordpressBlogName="Name"
Values filemany / repeatable / in Git--values custom-values.yaml
Edit chart locallyinspect or modify chart fileshelm pull --untar bitnami/wordpress && helm install my-release ./wordpress

[!TIP] Fastest way to see what is configurable: helm show values bitnami/wordpress > values.yaml (dumps chart defaults without pulling the whole chart; also §13.8), edit, then helm install … -f values.yaml.

[!WARNING] Exam traps / remember: --set beats -f regardless of order on the command line; among -f files the rightmost wins. A typo'd key (--set replicacount=3) is silently ignored — check with helm get values <release> or helm template.

[!NOTE] Beyond the course: --set-string key=val forces string type (e.g. "0123", "true"); lists via --set 'list={a,b}' or --set servers[0].port=80; escape dots in keys with \. (e.g. annotations).

13.8 Lifecycle management with Helm

Source: KodeKloud notes

Releases as the unit of lifecycle

  • Prereq: Helm + kubectl installed, KUBECONFIG pointing at a working cluster.
  • A release = installed chart + the objects it created. Helm knows which objects belong to which release → upgrade, rollback (downgrade) and uninstall one release without touching others, even from the same chart.
helm install my-site bitnami/wordpress
helm install my-second-site bitnami/wordpress    # managed independently

[!CAUTION] Correction: the original uses helm install my-SECOND-site …. Release names must be lowercase DNS-style names ([a-z0-9-.], max 53 chars); uppercase is rejected by Helm 3. Use my-second-site.

Install a specific chart version → upgrade → history → rollback

helm install nginx-release bitnami/nginx --version 7.1.0    # --version = CHART version, not app version
kubectl get pods                                            # nginx-release-687cdd5c75-ztn2n
kubectl describe pod nginx-release-687cdd5c75-ztn2n | grep Image:
#   Image: docker.io/bitnami/nginx:1.19.2-debian-10-r28     → app 1.19.2

helm upgrade nginx-release bitnami/nginx                    # no --version → latest chart
# Release "nginx-release" has been upgraded. Happy Helming!
# REVISION: 2   CHART VERSION: 9.5.13   APP VERSION: 1.21.4
kubectl get pods                                            # old pod terminated, new pod nginx-release-7b78f4fdcd-2zr7k
kubectl describe pod nginx-release-7b78f4fdcd-2zr7k | grep Image:
#   Image: docker.io/bitnami/nginx:1.21.4-debian-10-rXX

helm list
# NAME           NAMESPACE  REVISION  STATUS    CHART         APP VERSION
# nginx-release  default    2         deployed  nginx-9.5.13  1.21.4

helm history nginx-release
# REVISION  STATUS      CHART         APP VERSION  DESCRIPTION
# 1         superseded  nginx-7.1.0   1.19.2       Install complete
# 2         deployed    nginx-9.5.13  1.21.4       Upgrade complete

helm rollback nginx-release 1
# Rollback was a success! Happy Helming!
helm history nginx-release
# 1         superseded  nginx-7.1.0   1.19.2       Install complete
# 2         superseded  nginx-9.5.13  1.21.4       Upgrade complete
# 3         deployed    nginx-7.1.0   1.19.2       Rollback to 1
  • Upgrade: Helm computes the delta between current and desired manifests and applies it (Deployment rolls Pods).
  • Rollback re-applies the manifests of the old revision and records a NEW revision (3), it does not move back to revision 1.

Revision timeline: install creates revision 1, upgrade revision 2, rollback to 1 creates revision 3 with revision 1's manifests; data on PVs is not rolled back Diagram (new): how install/upgrade/rollback map to revisions and statuses.

PurposeCommand
List releaseshelm list
Release historyhelm history <release>
Roll backhelm rollback <release> <revision>

Persistent data is not rolled back

  • Rollback restores Kubernetes manifests (Deployments, StatefulSets, Services…), not data on PVs or in external databases.
  • For data-bearing apps: consistent backups (snapshots, DB dumps) before upgrading; pre-upgrade hooks (Jobs that snapshot DBs / copy data out, or validate/prepare before new manifests apply) or external backup tooling / operators.

Chart-enforced upgrade requirements (Bitnami WordPress)

  • Some charts refuse to upgrade without current credentials (DB migrations etc. need them):
$ helm upgrade wordpress-release bitnami/wordpress
Error: UPGRADE FAILED: ... PASSWORDS ERROR: You must provide your current passwords when upgrading the release.
  Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims.
'wordpressPassword' must not be empty, please add '--set wordpressPassword=$WORDPRESS_PASSWORD' ...
'mariadb.auth.rootPassword' must not be empty ...
'mariadb.auth.password' must not be empty ...
# Retrieve current passwords from the release's Secrets, then pass them back on upgrade
export WORDPRESS_PASSWORD=$(kubectl get secret --namespace "default" wordpress-release -o jsonpath="{.data.wordpress-password}" | base64 --decode)
export MARIADB_ROOT_PASSWORD=$(kubectl get secret --namespace "default" wordpress-release-mariadb -o jsonpath="{.data.mariadb-root-password}" | base64 --decode)
export MARIADB_PASSWORD=$(kubectl get secret --namespace "default" wordpress-release-mariadb -o jsonpath="{.data.mariadb-password}" | base64 --decode)

helm upgrade wordpress-release bitnami/wordpress \
  --set wordpressPassword=$WORDPRESS_PASSWORD \
  --set mariadb.auth.rootPassword=$MARIADB_ROOT_PASSWORD \
  --set mariadb.auth.password=$MARIADB_PASSWORD
  • Before upgrading: read chart docs and defaults (helm show values <chart>), supply required parameters via --set/values file, back up PV-backed workloads.

Best practices

  • Helm release = source of truth for the app's resource declarations (avoid out-of-band kubectl edits).
  • helm history + helm rollback for manifest-level reverts.
  • Data apps: read upgrade requirements (passwords, migrations), back up first, consider hooks/operators.
  • Test upgrades in staging before production.

[!WARNING] Exam traps / remember:

  • helm upgrade without --version jumps to the latest chart; pin with --version when a task names one.
  • helm list's REVISION and helm history count every install/upgrade/rollback; after one upgrade + one rollback you are on revision 3.
  • Find a release's app/chart version from helm list (CHART / APP VERSION columns) or the Pod image (kubectl describe pod).

[!NOTE] Beyond the course: helm rollback <release> with no revision → previous revision. helm upgrade --install creates the release if missing. If an upgrade passes any -f/--set, values set in earlier revisions but not repeated revert to chart defaults unless you add --reuse-values (with no overrides at all, Helm reuses the previous values). helm uninstall --keep-history retains revision Secrets. PVCs created from StatefulSet volumeClaimTemplates are not deleted by helm uninstall. The docker:// container IDs in the course output come from the old dockershim runtime; current nodes show containerd://.

  • The failure comes from the chart, not Kubernetes. Templates can call fail (here templates/NOTES.txt → common.errors.upgrade.passwords.empty) to abort helm upgrade at render time when required values are missing. The error text itself prints the exact export …=$(kubectl get secret … | base64 --decode) commands to run.

  • The course’s Bitnami nginx Pod listens on 8080/TCP (non-root image); compare Service targetPort and port-forward targets with the actual container listener.


← 12 Troubleshooting · Index · 14 Kustomize Basics (2025 Updates) →


Reference

Based on the KodeKloud Certified Kubernetes Administrator (CKA) course notes.

On this page