Helm — The Kubernetes Package Manager
Helm is the de-facto package manager for Kubernetes. It packages K8s manifests into reusable charts, tracks deployed releases, and supports rollbacks.
Core Concepts
| Term | Description |
|---|---|
| Chart | A package of K8s templates + default values |
| Release | A deployed instance of a chart (you can deploy the same chart multiple times) |
| Repository | A HTTP server hosting charts (like npm registry) |
| Values | YAML configuration that overrides chart defaults |
| Template | Go-templated K8s manifest inside a chart |
Installation
# macOS
brew install helm
# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Verify
helm version
Repository Management
# Add popular repos
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add cert-manager https://charts.jetstack.io
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add argo https://argoproj.github.io/argo-helm
# Update all repos (like apt-get update)
helm repo update
# Search for a chart
helm search repo nginx
helm search repo bitnami/postgres
helm search hub wordpress # search ArtifactHub (public index)
# Show available versions
helm search repo bitnami/postgresql --versions | head -10
Installing Charts
# Basic install
helm install my-nginx bitnami/nginx
# Install into a specific namespace (creates namespace if --create-namespace)
helm install my-nginx bitnami/nginx -n web --create-namespace
# Override values inline
helm install my-nginx bitnami/nginx \
--set replicaCount=3 \
--set service.type=LoadBalancer
# Override with a values file (recommended for complex config)
helm install my-nginx bitnami/nginx -f values-prod.yaml
# Combine file + inline (inline wins on conflicts)
helm install my-nginx bitnami/nginx -f values-prod.yaml --set image.tag=1.27
# Install specific version
helm install my-nginx bitnami/nginx --version 15.3.5
# Dry run (show what would be deployed without applying)
helm install my-nginx bitnami/nginx --dry-run --debug
# Wait until deployment is ready
helm install my-nginx bitnami/nginx --wait --timeout 5m
Managing Releases
# List releases
helm list
helm list -n web
helm list -A # all namespaces
helm list --failed # only failed releases
# Check release status
helm status my-nginx
# Show release history (rollback targets)
helm history my-nginx
# Upgrade (update config or chart version)
helm upgrade my-nginx bitnami/nginx --set replicaCount=5
helm upgrade my-nginx bitnami/nginx -f values-prod.yaml --version 15.4.0
# Install OR upgrade (idempotent — great for CI/CD)
helm upgrade --install my-nginx bitnami/nginx -f values-prod.yaml -n web --create-namespace
# Rollback to a previous revision
helm rollback my-nginx 1 # rollback to revision 1
helm rollback my-nginx # rollback to previous revision
# Uninstall (keeps history by default)
helm uninstall my-nginx
helm uninstall my-nginx --keep-history # keep history for rollback
Inspecting Charts
# Show default values
helm show values bitnami/nginx
helm show values bitnami/nginx > values.yaml # save to file for customization
# Show chart metadata
helm show chart bitnami/nginx
# Show all chart info (values + chart + README)
helm show all bitnami/nginx
# Show what will be deployed (render templates locally)
helm template my-nginx bitnami/nginx -f values.yaml
# Show differences before upgrade
helm diff upgrade my-nginx bitnami/nginx -f values.yaml # requires helm-diff plugin
Creating Your Own Chart
# Scaffold a new chart
helm create myapp
# Structure created:
# myapp/
# ├── Chart.yaml # chart metadata (name, version, appVersion)
# ├── values.yaml # default configuration
# ├── charts/ # chart dependencies
# └── templates/
# ├── deployment.yaml
# ├── service.yaml
# ├── ingress.yaml
# ├── serviceaccount.yaml
# ├── hpa.yaml
# ├── NOTES.txt # shown after install
# └── _helpers.tpl # reusable template snippets
Chart.yaml
apiVersion: v2
name: myapp
description: My application Helm chart
type: application
version: 1.0.0 # chart version — bump on chart changes
appVersion: "2.3.1" # application version (informational)
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
values.yaml
replicaCount: 2
image:
repository: myregistry/myapp
tag: "2.3.1"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: nginx
host: myapp.example.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
postgresql:
enabled: true
auth:
database: myapp
username: myapp
existingSecret: db-secret
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 80
resources:
{{- toYaml .Values.resources | nindent 10 }}
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
_helpers.tpl (reusable snippets)
{{/*
Expand the name of the chart.
*/}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Full name — release + chart name
*/}}
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name (include "myapp.name" .) | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{ include "myapp.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Chart Dependencies
# Download dependencies defined in Chart.yaml
helm dependency update myapp/
# List dependencies
helm dependency list myapp/
Packaging & Publishing
# Lint a chart (check for errors)
helm lint myapp/
# Package chart into .tgz
helm package myapp/
# → myapp-1.0.0.tgz
# Push to OCI registry (Helm 3.8+)
helm push myapp-1.0.0.tgz oci://registry.example.com/charts
# Install from OCI
helm install myapp oci://registry.example.com/charts/myapp --version 1.0.0
# Host a chart repo (serve directory as HTTP)
helm repo index ./charts --url https://charts.example.com
Useful Helm Plugins
# helm-diff — show diff before upgrade
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade my-nginx bitnami/nginx -f values.yaml
# helm-secrets — encrypt values with SOPS/Vault
helm plugin install https://github.com/jkroepke/helm-secrets
# helm-unittest — unit test chart templates
helm plugin install https://github.com/helm-unittest/helm-unittest
Helm in CI/CD (Best Practices)
# Always use --atomic in CI (rolls back on failure)
helm upgrade --install myapp ./charts/myapp \
-f values-prod.yaml \
--namespace production \
--create-namespace \
--atomic \
--timeout 10m \
--wait
# Set image tag from CI pipeline variable
helm upgrade --install myapp ./charts/myapp \
--set image.tag=$CI_COMMIT_SHA \
-f values-prod.yaml
# Use --reuse-values to preserve existing config when bumping chart version
helm upgrade myapp bitnami/nginx --reuse-values --version 15.5.0
Common Interview Questions
Q: Difference between helm install and helm upgrade --install?
helm installfails if the release already exists.helm upgrade --installcreates it if missing or upgrades if it exists — idempotent and preferred in CI/CD.
Q: How do you manage secrets in Helm?
Don't put raw secrets in values files. Options: (1) Reference existing K8s Secrets by name in values, (2) Use
helm-secretsplugin with SOPS/Vault, (3) Use External Secrets Operator, (4) Pass--set db.password=$SECRETfrom CI env vars.
Q: What is the difference between chart version and appVersion?
versionis the Helm chart version — bump it when you change templates or values schema.appVersionis the deployed application version — informational only, used by default as the image tag.
Q: How do you roll back a Helm release?
helm rollback <release> <revision>. Helm stores all revision history in the cluster (as Secrets). View withhelm history <release>.