Backend / Kubernetes / 06_helm.md

Helm

Updated 5 interview angles 4 min read source
On this page9
  1. Why it exists
  2. Chart layout
  3. Installing / upgrading
  4. Values precedence
  5. Common patterns
  6. Helm vs Kustomize
  7. GitOps with Helm
  8. Common bugs
  9. Interview angle

Helm

The package manager for Kubernetes. Helm bundles related k8s manifests (Deployment + Service + ConfigMap + Ingress + …) into a versioned, parameterizable chart.

Why it exists

Without Helm, deploying a non-trivial app means kubectl apply -f over a dozen YAML files, with environment-specific substitutions done manually or via a templating tool. Helm gives you:

  • Templating — one set of YAML with {{ .Values.x }} placeholders, rendered per environment.
  • Packaging — a chart is a versioned tarball you can publish/share.
  • Release tracking — Helm remembers what version of what chart with what values is currently installed; rollback to previous revision in one command.
  • Dependencies — chart A can declare chart B as a dependency.

Chart layout

text
orders/
├── Chart.yaml          # name, version, dependencies
├── values.yaml         # default values
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── configmap.yaml
│   ├── _helpers.tpl    # reusable template snippets
│   └── NOTES.txt       # printed after install
└── charts/             # sub-chart dependencies

Chart.yaml:

yaml
apiVersion: v2
name: orders
description: Orders service
type: application
version: 1.2.3              # the chart version
appVersion: "1.0.0"         # the app image version (informational)
dependencies:
- name: postgresql
  version: "13.x.x"
  repository: https://charts.bitnami.com/bitnami

values.yaml:

yaml
replicaCount: 3
image:
  repository: my-org/orders
  tag: "1.0.0"
resources:
  requests: { cpu: 200m, memory: 256Mi }
  limits:   { cpu: 1,    memory: 512Mi }
ingress:
  enabled: true
  host: api.example.com

A template using values:

yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-orders
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
      - name: app
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        resources: {{- toYaml .Values.resources | nindent 10 }}

Installing / upgrading

bash
# Install
helm install orders ./orders -n prod -f values-prod.yaml

# Upgrade or install (idempotent)
helm upgrade --install orders ./orders -n prod -f values-prod.yaml

# See what's deployed
helm list -n prod
helm get values orders -n prod
# rendered YAML actually applied
helm get manifest orders -n prod

# Roll back
helm history orders -n prod
# back to revision 3
helm rollback orders 3 -n prod

helm template (no install) renders to stdout — useful for CI dry-runs and gitops.

Values precedence

From lowest to highest:

  1. values.yaml in the chart.
  2. Sub-chart values.
  3. -f values-prod.yaml on the CLI.
  4. --set key=value on the CLI.

Per-environment pattern: one chart, N values files (values-dev.yaml, values-staging.yaml, values-prod.yaml).

Common patterns

A _helpers.tpl for shared snippets

yaml
{{- define "orders.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end -}}
yaml
metadata:
  labels: {{- include "orders.labels" . | nindent 4 }}

Reusable; consistent labels everywhere.

Conditionally render

yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-orders
spec:
  rules:
  - host: {{ .Values.ingress.host }}
    ...
{{- end }}

Hooks for migrations

yaml
metadata:
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded

Runs a Job before the Deployment is applied — good place for alembic upgrade head. Caveat: hooks aren’t transactional with the rest of the release; a failing hook leaves a half-applied state.

Helm vs Kustomize

Helm Kustomize
Approach Go templating overlay/patch
Best for parameterizable charts you share environment-specific overlays of your own YAML
Tooling separate helm CLI built into kubectl apply -k
Complexity indent gymnastics in templates YAML-native, no templating

Many teams use both: Helm for third-party charts (postgres, kafka, cert-manager), Kustomize for in-house apps. Or skip Helm entirely for in-house and use Kustomize + GitOps.

GitOps with Helm

ArgoCD or Flux render charts and apply them, with the git repo as the source of truth.

yaml
# Argo Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: orders, namespace: argocd }
spec:
  source:
    repoURL: https://github.com/myorg/charts
    path: orders
    targetRevision: main
    helm:
      valueFiles: [values-prod.yaml]
  destination: { server: https://kubernetes.default.svc, namespace: prod }
  syncPolicy:
    automated: { prune: true, selfHeal: true }

Change values in git → Argo detects → applies. No one runs helm upgrade from a laptop.

Common bugs

  • Indentation hell. nindent + toYaml for nested objects, otherwise you get cryptic YAML errors. helm template to see the rendered output.
  • Forgetting --namespace. Helm doesn’t infer namespace from kubectl config; default is default.
  • .Release.Name collisions. Two releases of the same chart in one namespace → resources collide.
  • Secrets in values files. Don’t put secrets in values-prod.yaml in git. Use external secret store + reference, or SOPS-encrypted values, or sealed-secrets.
  • Hook failures leave state half-applied. Hooks don’t rollback the release on failure; the migration Job fails, the Deployment is half-rolled-out.
  • Chart bloat. if / range everywhere makes templates unreadable. If a chart has >10 toggles, you’re probably misusing it — split it.

Interview angle 5

  • “What is Helm?” — package manager for Kubernetes; charts bundle parameterizable YAML with versioning, dependencies, and release tracking. Replaces hand-managing 10+ YAML files per app.
  • “Helm vs Kustomize?” — Helm is templating + packaging (share charts across orgs). Kustomize is overlay-based (environment-specific patches). Use Helm for third-party (postgres, kafka); Kustomize fits in-house apps with environment variants.
  • “How do you do DB migrations with Helm?”pre-upgrade hook runs a Job that runs alembic upgrade head (or equivalent) before the new pods come up. Watch for: hook failures leaving release in inconsistent state; race with multi-pod migrations.
  • “How do you parameterize for multiple environments?” — one chart + one values.yaml per environment (values-dev.yaml, values-prod.yaml), applied with helm upgrade --install -f values-prod.yaml. Don’t fork the chart per env.
  • “Where do secrets live in a Helm chart?” — never plain in values.yaml in git. Either External Secrets Operator + reference in the chart, or SOPS-encrypted values, or sealed-secrets.