Cracking ML Interviews

Open in the interactive study hub

Helm: The Kubernetes Package Manager

Helm is the package manager for Kubernetes. It lets you define, install, and upgrade complex Kubernetes applications using reusable packages called charts.


What is Helm?

Without Helm, deploying an application on Kubernetes means managing dozens of YAML files (Deployments, Services, ConfigMaps, Secrets, Ingress, HPAs...). Helm bundles all of these into a single chart with templating and versioning.

Helm Chart  →  helm install  →  Release (running app in K8s)
Helm Concept Kubernetes Analogy
Chart Package (like a Homebrew formula)
Release Installed instance of a chart
Repository Package registry (like npm/PyPI)
Values Configuration for a release

Installation

# macOS
brew install helm

# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify
helm version

All commands in this guide are Helm 3 or later. Helm 2 (with the in-cluster Tiller server and helm init) is long unsupported. Helm 4 was released in late 2025; the everyday commands below carry over, but read its migration notes before upgrading CI or plugins.


Core Workflow

# Add a chart repository
# (The old "stable" repo at charts.helm.sh/stable is archived: find charts on Artifact Hub instead.)
# Bitnami moved its charts to OCI and restricted its free catalog in 2025: check current terms first.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update   # refresh repo index

# Search for charts
helm search repo postgres
helm search hub redis    # search Artifact Hub

# Install a chart (creates a release)
helm install my-postgres bitnami/postgresql \
  --namespace db \
  --create-namespace \
  --set auth.postgresPassword=secret

# Install with custom values file
helm install my-app ./my-chart -f values-production.yaml

# List releases
helm list
helm list -A   # all namespaces

# Upgrade a release
helm upgrade my-postgres bitnami/postgresql \
  --set auth.postgresPassword=newsecret

# Upgrade or install (idempotent)
helm upgrade --install my-app ./my-chart -f values.yaml

# Rollback to previous release
helm rollback my-postgres 1

# Uninstall
helm uninstall my-postgres

# Release history
helm history my-postgres

Chart Structure

my-chart/
├── Chart.yaml              # Chart metadata
├── values.yaml             # Default values
├── charts/                 # Chart dependencies
├── templates/              # Kubernetes manifests (templated)
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── _helpers.tpl        # Named template helpers
│   └── NOTES.txt           # Post-install instructions
└── .helmignore

Chart.yaml

apiVersion: v2
name: my-app
description: My application Helm chart
type: application
version: 1.2.0         # Chart version
appVersion: "2.5.1"   # App version (Docker image tag)

dependencies:
  - name: postgresql
    version: "~12.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled

values.yaml

# Default configuration
replicaCount: 2

image:
  repository: myrepo/my-app
  tag: ""   # defaults to Chart.appVersion
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  className: "nginx"
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

postgresql:
  enabled: true
  auth:
    postgresPassword: ""   # override in production

env: {}
  # KEY: value

Templates

Templates are Kubernetes YAML + Go templating.

Deployment Template

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "my-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 8000
          {{- if .Values.env }}
          env:
            {{- range $key, $val := .Values.env }}
            - name: {{ $key }}
              value: {{ $val | quote }}
            {{- end }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

_helpers.tpl (Named Templates)

# templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

Template Syntax Reference

# Value access
{{ .Values.image.repository }}
{{ .Chart.Name }}
{{ .Release.Name }}
{{ .Release.Namespace }}

# Conditionals
{{- if .Values.ingress.enabled }}
...
{{- end }}

{{- if eq .Values.environment "production" }}
  replicas: 5
{{- else }}
  replicas: 1
{{- end }}

# Loops
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
{{- end }}

{{- range $key, $value := .Values.env }}
- name: {{ $key }}
  value: {{ $value | quote }}
{{- end }}

# String functions
{{ "hello" | upper }}           → HELLO
{{ .Values.name | trunc 63 }}   → truncate
{{ .Values.name | trimSuffix "-" }}
{{ .Values.name | replace "-" "_" }}
{{ .Values.config | toYaml | nindent 4 }}
{{ .Values.password | b64enc }}
{{ .Values.password | quote }}
{{ printf "%s-%s" .Release.Name .Chart.Name }}

# Default values
{{ .Values.image.tag | default "latest" }}
{{ .Values.replicas | default 1 }}

# Whitespace control (- trims whitespace)
{{- /* trim leading whitespace */ -}}

Values Override Strategies

# Override individual values
helm install my-app ./chart --set replicaCount=3
helm install my-app ./chart --set image.tag=2.0 --set ingress.enabled=true

# Override with values file
helm install my-app ./chart -f values-production.yaml

# Multiple values files (merged in order, last wins)
helm install my-app ./chart \
  -f values.yaml \
  -f values-production.yaml \
  -f secrets.yaml

# View effective values
helm get values my-app
helm get values my-app --all   # including defaults

Environment-specific Values

# values-production.yaml
replicaCount: 5

image:
  tag: "1.5.2"

ingress:
  enabled: true
  hosts:
    - host: api.myapp.com

resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: 2
    memory: 2Gi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20

Debugging & Testing

# Render templates without installing (dry run)
helm template my-app ./chart -f values.yaml

# Dry run with server validation (plain --dry-run is client-only since Helm 3.13)
helm install my-app ./chart --dry-run=server

# Debug rendered templates
helm template my-app ./chart --debug

# Validate chart
helm lint ./chart
helm lint ./chart -f values-production.yaml

# Inspect installed release
helm get all my-app
helm get manifest my-app
helm get notes my-app

# Test release
helm test my-app

OCI Registries (2026 Standard)

Helm 3.8+ supports storing charts in OCI registries (like Docker Hub, GHCR, ECR).

# Push chart to OCI registry
helm package ./my-chart
helm push my-chart-1.0.0.tgz oci://ghcr.io/myorg/charts

# Pull and install from OCI
helm install my-app oci://ghcr.io/myorg/charts/my-chart --version 1.0.0

# Login to OCI registry
echo "$GITHUB_TOKEN" | helm registry login ghcr.io -u "$GITHUB_USER" --password-stdin

# Ingress NGINX (the community controller was retired in 2026; prefer a
# Gateway API implementation or another maintained controller for new clusters)
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace

# cert-manager (TLS automation)
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true   # replaces the deprecated installCRDs flag

# Prometheus + Grafana
helm upgrade --install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace

# ArgoCD
helm upgrade --install argocd argo/argo-cd \
  --namespace argocd --create-namespace

# External Secrets Operator
helm upgrade --install external-secrets \
  external-secrets/external-secrets \
  --namespace external-secrets --create-namespace

# KEDA (event-driven autoscaling)
helm upgrade --install keda kedacore/keda \
  --namespace keda --create-namespace

Cheat Sheet

# Repository
helm repo add name url
helm repo update
helm repo list
helm search repo term

# Install / Upgrade
helm install release chart [-f vals] [--set k=v]
helm upgrade release chart
helm upgrade --install release chart   # upsert
helm uninstall release [-n namespace]

# Inspect
helm list [-A]
helm status release
helm get values release [--all]
helm get manifest release
helm history release

# Rollback
helm rollback release [revision]

# Templates
helm template release chart [-f vals]
helm lint chart [-f vals]

# Package & Push (OCI)
helm package ./chart
helm push chart.tgz oci://registry/path

View or edit this guide on GitHub