All topics
DevOps · Learning hub

Helm notes for developers

Master Helm with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallTest yourself — Helm quizMore DevOps notes
Helm

Helm Essentials

Helm Essentials Helm is the package manager for Kubernetes. Instead of hand-maintaining a pile of raw YAML manifests for every environment, you package an appli

Helm Essentials

Helm is the package manager for Kubernetes. Instead of hand-maintaining a pile of raw YAML manifests for every environment, you package an application's manifests into a chart — a versioned, templated bundle — and install, upgrade, or roll it back as a single unit called a release. Helm is the tool most teams reach for the moment kubectl apply -f across a dozen slightly-different YAML files per environment stops scaling.

Chart Structure

A chart is just a directory with a fixed layout. helm create mychart scaffolds one:

mychart/
  Chart.yaml          # name, version, appVersion, dependencies
  values.yaml         # default configuration values
  charts/             # bundled subcharts (dependencies)
  templates/
    deployment.yaml
    service.yaml
    ingress.yaml
    _helpers.tpl       # reusable named template snippets
    NOTES.txt          # printed to the user after install/upgrade
  .helmignore

# Chart.yaml
apiVersion: v2
name: mychart
description: A Helm chart for my app
version: 1.2.0        # chart version — bump on every change to the chart itself
appVersion: "2.4.0"   # version of the application it deploys (informational)
dependencies:
  - name: redis
    version: "18.x.x"
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled

The distinction between version and appVersion trips people up constantly: version is the chart's own release number (SemVer, required); appVersion just documents which version of the underlying app it ships — Helm never parses or enforces it.

Templates & values.yaml

Templates are Kubernetes manifests with Go template syntax layered on top. Values from values.yaml (or -f custom-values.yaml / --set overrides at install time) get injected via {{ .Values.* }}.

# values.yaml
replicaCount: 2
image:
  repository: myorg/myapp
  tag: "1.4.0"
  pullPolicy: IfNotPresent
resources:
  requests:
    cpu: 100m
    memory: 128Mi
autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "mychart.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          ports:
            - containerPort: 8080

Use helm template . to render the manifests locally without touching a cluster, and helm lint . to catch template and schema mistakes before you ever install. Both should be part of every chart's CI.

Releases: Install, Upgrade, Rollback

Every helm install creates a named release with its own revision history, tracked as Kubernetes Secrets in the target namespace by default. That history is what makes helm rollback possible — it's not magic, Helm just re-applies a prior rendered manifest set.

# First install
helm install myapp ./mychart -f values-prod.yaml --namespace prod --create-namespace

# Idempotent install-or-upgrade — the workhorse command for CI/CD
helm upgrade --install myapp ./mychart -f values-prod.yaml \
  --namespace prod \
  --set image.tag=$CI_COMMIT_SHORT_SHA \
  --atomic --wait --timeout 5m

# Inspect history and roll back if a deploy goes bad
helm history myapp -n prod
helm rollback myapp 3 -n prod

# See what would change without applying anything
helm diff upgrade myapp ./mychart -f values-prod.yaml   # requires helm-diff plugin

helm uninstall myapp -n prod

`--atomic` rolls the release back automatically if the upgrade fails, and `--wait` blocks until resources report ready — together they turn a Helm upgrade into something closer to a real deployment gate instead of a fire-and-forget `kubectl apply`.

Repositories & Hooks

Charts are distributed through repositories — an index of packaged .tgz charts, often just static files served over HTTP or an OCI registry. Hooks let a chart run one-off jobs at specific points in a release's lifecycle, like a DB migration before a new version of the app comes up.

# Add and use a chart repo
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/postgresql
helm install my-db bitnami/postgresql --version 13.2.0

# Pull an OCI-based chart (modern registries, e.g. GHCR, ECR)
helm install myapp oci://ghcr.io/myorg/charts/myapp --version 1.2.0

# templates/migration-job.yaml — a pre-upgrade hook
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-db-migrate
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["./manage.py", "migrate"]

`hook-weight` controls ordering when multiple hooks share the same event, and `hook-delete-policy` decides whether the Job resource is cleaned up automatically — without it, failed migration Jobs pile up and block the next release from creating a Job with the same name.

Practical Tips & Gotchas

  • Use helm upgrade --install in CI, never plain helm install — the latter fails outright if the release already exists.

  • Helm does not delete resources it no longer manages when you rename or remove a template — orphaned resources need manual cleanup or `helm uninstall` + reinstall.

  • Secrets should never live in `values.yaml` committed to git — use `--set-file`, a secrets operator (e.g. Sealed Secrets, External Secrets), or `helm-secrets` with SOPS instead.

  • `--set` values are strings by default and get parsed as YAML — `--set replicas=3` becomes a number, but `--set image.tag=1.0` can surprise you; quote ambiguous values explicitly.

  • Chart release history accumulates as Secrets in-cluster; `helm history` growing unbounded on high-churn releases is normal but worth pruning with `--history-max` on install.

  • Prefer named templates in `_helpers.tpl` over copy-pasted label blocks — every resource needing the same `selectorLabels` is a bug waiting to happen if they drift.

Keep your Helm knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever