Gendi Notes

Helm

Helm is a templating engine plus a release ledger. Separating those two jobs in your head explains every Helm problem you will have.

Level 2 guide kuberneteshelmpackaging

What is it?#

Helm packages a set of Kubernetes manifests into a chart — templated YAML plus a file of default values — and installs it as a named release it keeps track of.

CHART VALUES RELEASE ───── ────── ─────── templates/*.yaml + values.yaml ──→ plain Kubernetes YAML with {{ }} holes your settings render applied to the cluster │ └─ and a record of WHAT was applied, so it can be undone

Why it exists#

Without it you hit three problems, in this order:

ProblemThe manual workaroundWhy it fails
The same app in dev/staging/prodCopy the YAML three timesThey drift; a fix lands in one
Installing someone else's softwareRead their docs, copy 15 manifestsNo upgrade path, no uninstall
Undoing a bad deploykubectl apply the old files — if you kept themYou did not keep them

What it is made of#

mychart/ ├── Chart.yaml name, version, appVersion, dependencies ├── values.yaml DEFAULT values — the chart's public API ├── templates/ │ ├── deployment.yaml Go templates that render to manifests │ ├── service.yaml │ ├── _helpers.tpl reusable named templates (leading _ = not rendered) │ └── NOTES.txt printed after install └── charts/ vendored subcharts (dependencies)
PieceWhat it is
ChartThe package: templates + defaults + metadata
ValuesThe inputs. Layered: chart defaults → -f file → --set
ReleaseOne installation of a chart, with a name and a revision number
RepositoryWhere charts are hosted (an HTTP index, or an OCI registry)
RevisionA numbered snapshot of a release, stored as a Secret in-cluster

How to use it#

Installing something that already exists#

The commands you will use daily
# 1. Add a repository and refresh the index
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# 2. Look before you install — what can this chart be configured with?
helm show values bitnami/redis | less

# 3. Install as a NAMED release. The name is how you refer to it forever.
helm install my-redis bitnami/redis \
  --namespace data --create-namespace \
  --set auth.password=... \
  --version 20.1.0            # PIN the chart version. Always.

# 4. What is installed on this cluster?
helm list -A

The three commands that prevent most mistakes#

# Render locally — no cluster contact. Catches template errors instantly.
helm template my-redis bitnami/redis -f prod-values.yaml

# Dry run against the cluster — also runs server-side validation
helm install my-redis bitnami/redis --dry-run --debug

# See exactly what an upgrade WOULD change (needs the helm-diff plugin)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade my-redis bitnami/redis -f prod-values.yaml

Writing a chart#

templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  # include a named template from _helpers.tpl; nindent keeps YAML valid
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "mychart.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          # default lets the chart work with no values supplied
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          ports:
            - containerPort: {{ .Values.service.port }}
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
values.yaml — the chart's public API. Document it.
replicaCount: 2
image:
  repository: nginx
  tag: ""              # empty → falls back to Chart.appVersion
service:
  port: 80
resources:
  requests: { cpu: 100m, memory: 128Mi }
  limits:   { memory: 256Mi }

Upgrading and rolling back#

# Upgrade, or install if it does not exist yet — the CI-friendly form
helm upgrade --install my-redis bitnami/redis \
  -f prod-values.yaml \
  --version 20.1.0 \
  --atomic \                 # roll back automatically if it fails
  --wait --timeout 5m        # wait for resources to be Ready

# History and rollback
helm history my-redis
helm rollback my-redis 3     # back to revision 3

What goes wrong#

SymptomCauseFix
cannot re-use a name that is still in useA release with that name existshelm list -A; use upgrade --install
has no deployed releasesA previous install failed and left a failed releasehelm uninstall then reinstall, or --force
did not find expected keyIndentation from indent vs nindentRender with helm template and read the output
Upgrade hangs forever--wait with pods that never become Readykubectl describe a new pod; check the probes
field is immutableChanging a Deployment selector, or a PVC size downwardDelete and recreate, or use a new resource name
Values seem ignoredWrong key path, or precedencehelm get values <release> shows what was actually used
Rollback does not restore dataHelm manages manifests, not PVC contentsRestore the data separately; Helm never touches volumes
Secrets visible in helm get valuesValues are stored in a release SecretKeep secrets out of values; use a secret manager

Helm versus Kustomize#

Both solve "the same manifests, different per environment", differently.

HelmKustomize
MechanismTemplate, then renderPatch a base with overlays
Inputvalues.yamlkustomization.yaml patches
Valid YAML before renderingNo — it is Go templatesYes — always real YAML
Release trackingYes, with rollbackNo — it just produces YAML
Third-party softwareThe ecosystem standardRare
Complexity ceilingNested conditionals get unreadable fastDeep overlay chains get hard to trace
Built into kubectlNoYes — kubectl apply -k

A reasonable default: Kustomize for your own applications, Helm for installing other people's. They combine — Kustomize can post-process rendered Helm output, and Argo CD supports that directly.

Interview corner · الأسئلة اللي بتتسأل#

What does Helm give you that kubectl apply -f does not?

A release ledger. kubectl apply is stateless — it knows only the files in front of it right now.

Three consequences:

kubectl apply -f ./manifestshelm upgrade
Delete a manifest from the directoryObject stays in the cluster forever, orphanedRemoved, because Helm diffs against the previous release
Undo a bad deployRe-apply old files — if you still have themhelm rollback <release> <revision>
"What is installed here?"Inspect the cluster and guesshelm list -A

Plus templating, which Kustomize also solves — so the ledger is the part that is genuinely Helm's.

A helm upgrade fails halfway. What state is the cluster in, and what do you do?

Partially applied, unless you used --atomic.

Helm applies resources in a dependency-aware order. If it fails at resource 12 of 20, the first 11 are live and the release is marked failed.

helm history my-app            # find the last DEPLOYED revision
helm status my-app             # what state is it in now
kubectl describe pod <new>     # why did it actually fail
helm rollback my-app <n>       # back to a known-good revision

The trap: if the first ever install failed, there is no previous revision, and helm upgrade then fails with has no deployed releases. You must helm uninstall and install again.

The prevention, and the real answer:

helm upgrade --install my-app ./chart --atomic --wait --timeout 5m

--wait makes Helm block until resources are actually Ready rather than merely accepted, and --atomic rolls back automatically on failure — so the cluster is never left half-upgraded.

Where does Helm store release state, and why does that matter?

In Secrets in the release's namespace, one per revision, named sh.helm.release.v1.<release>.v<n> and containing a gzipped, base64-encoded copy of the rendered manifests and the values used.

kubectl get secret -n <ns> -l owner=helm
helm get values my-app          # the values that were actually applied
helm get manifest my-app        # the YAML that was actually applied

Three consequences that matter operationally:

  • State lives in the cluster, not on your laptop. Any machine with credentials sees the same history — which is what makes Helm usable from CI.
  • Lose the namespace, lose the history. Deleting a namespace destroys the release records with it; helm rollback is then impossible.
  • Anyone who can read Secrets in that namespace can read your values — including any password you passed with --set. This is the argument for keeping secrets out of values entirely and using a secret manager or External Secrets, with RBAC on the namespace.

Key takeaways#

  • Helm is a templating engine plus a release ledger. The ledger is the part kubectl apply cannot replace.
  • helm template renders without a cluster — read third-party charts with it before you trust them, and debug your own with it.
  • --atomic --wait in CI, always. Without them a failed upgrade leaves the release half-applied and reports success.
  • Pin the chart version with --version, exactly as you pin image tags.
  • Release state is Secrets in the namespace — so values are readable by anyone with Secret access, and deleting the namespace destroys the history.
  • helm rollback restores manifests, never data. Migrations must still be backwards-compatible.
  • Kustomize for your apps, Helm for other people's is a defensible default.

Last updated 2026-09-13