⛵ What is Helm?
›Kubernetes Package Manager
Without Helm, deploying to 3 environments means manually editing 5 YAML files, 3 times. With Helm, one chart deploys everywhere with different values. One command to upgrade. One command to rollback.
| Concept | What it is | Analogy |
|---|---|---|
| Chart | Package containing K8s templates + default values | npm package / apt package |
| Release | Installed instance of a chart with a name | Installed application |
| Repository | Collection of charts (hosted URL) | npm registry / apt repository |
| Values | Configuration that customises a chart | Config file |
| Revision | Numbered history of every upgrade/rollback | Git commit history |
🖥️ Essential Commands
›📦 Chart Structure
›Anatomy of a Helm chart
Charts follow a standard directory layout. Templates use Go template syntax with { "{" } .Values.key { "}" } for value substitution.
🔧 Writing Templates
›Go template syntax
Helm templates use Go's text/template engine. Key constructs: { "{" } .Values.x { "}" } inserts value, { "{" }- if .Values.enabled { "}" } conditional, { "{" }- range .Values.list { "}" } loop, { "{" } include "helper" . { "}" } calls a named template.
🌍 Multi-Environment Deployment
›🔍 Troubleshooting
›🔧 Named Templates and _helpers.tpl
›Reusable template snippets across your chart
The _helpers.tpl file (note the underscore — it is not rendered as a manifest) defines named templates that can be reused across all your chart templates. This avoids repeating the same label structure, name construction, or selector logic in every template file.
# templates/_helpers.tpl
{{- define "myapp.name" -}}
{{- .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "myapp.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Using named templates in manifests
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
Why this matters
Define labels once in _helpers.tpl → all your Deployment, Service, Ingress templates use the same labels via include. When you need to change a label, change it in one place. The | nindent N pipe indents the included block to match the YAML level.
📦 OCI Registries and Helm Chart Distribution
›Helm 3.8+ — Store charts in OCI registries (ACR, ECR, Docker Hub)
OCI (Open Container Initiative) support means you can push Helm charts to the same registries as Docker images. No need for a separate ChartMuseum or Helm HTTP repository.
# Push chart to Azure Container Registry (OCI) helm registry login myacr.azurecr.io --username admin --password $(az acr credential show --name myacr --query passwords[0].value -o tsv) helm push mychart-1.0.0.tgz oci://myacr.azurecr.io/helm/charts # Pull and install from OCI registry helm install myapp oci://myacr.azurecr.io/helm/charts/mychart --version 1.0.0 # Push to AWS ECR aws ecr get-login-password | helm registry login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com helm push mychart-1.0.0.tgz oci://123456789.dkr.ecr.us-east-1.amazonaws.com/helm-charts
Traditional HTTP repository (Helm 2 style — still common)
# Add and use a Helm repo helm repo add stable https://charts.helm.sh/stable helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update # Always update before installing # Search repo helm search repo bitnami/postgresql # Show chart values before installing helm show values bitnami/postgresql > postgres-values.yaml # Edit postgres-values.yaml to customise helm install my-postgres bitnami/postgresql -f postgres-values.yaml
🧪 Helm Chart Testing and Linting
›Test before you deploy — catch errors early
# Lint — check for common chart errors helm lint ./mychart helm lint ./mychart -f values-production.yaml # lint with specific values # Template rendering — see what manifests will be generated WITHOUT deploying helm template myrelease ./mychart -f values-prod.yaml helm template myrelease ./mychart -f values-prod.yaml | kubectl diff -f - # compare with live # Dry run — simulate install against live cluster (validates against K8s API) helm install myapp ./mychart --dry-run --debug -f values-prod.yaml # Helm test — run test Pods after deployment helm test myrelease # runs pods in templates/tests/ directory
Test hook example
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test # runs on: helm test myrelease
"helm.sh/hook-delete-policy": hook-succeeded
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health']
Chart versioning strategy
| Version field | What it means | When to increment |
|---|---|---|
| version in Chart.yaml | Chart version — packaging changes | Any change to templates, values structure, or chart logic |
| appVersion in Chart.yaml | Application version — the image tag | When the application code changes |