Helm
Overview
Helm is the package manager for Kubernetes. It bundles Kubernetes manifests into charts — versioned, shareable, and parameterizable. Helm handles installation, upgrades, rollbacks, and dependency management, making it the standard way to distribute and deploy Kubernetes applications.
Core commands
These are the commands you'll use most often when working with Helm: adding and searching chart repositories, installing and upgrading releases, rolling back a bad release, and inspecting what's currently deployed and with which values.
# Add a chart repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Search
helm search repo nginx
helm search repo bitnami/postgresql --versions
# Install a chart
helm install my-release bitnami/nginx
helm install my-release bitnami/nginx --namespace production --create-namespace
helm install my-release bitnami/nginx -f values-prod.yaml
helm install my-release bitnami/nginx --set replicaCount=3
# Dry-run: render the templates and show what would be applied, without touching the cluster
helm install my-release bitnami/nginx --dry-run --debug
helm template my-release bitnami/nginx -f values.yaml
# List releases
helm list
helm list -A # all namespaces
helm list --failed
helm list --pending
# Upgrade
helm upgrade my-release bitnami/nginx
helm upgrade my-release bitnami/nginx -f values-prod.yaml
helm upgrade my-release bitnami/nginx --set image.tag=1.25
# Rollback
helm rollback my-release 1 # rollback to revision 1
helm rollback my-release # rollback to previous revision
helm history my-release # list revisions
# Uninstall
helm uninstall my-release
helm uninstall my-release --keep-history # keep release history for rollback
# Inspect
helm get values my-release # current values overrides
helm get manifest my-release # rendered manifests
helm get notes my-release # release notes
helm get all my-release # all information
helm show values bitnami/nginx # chart's default values
Chart structure
A chart is simply a directory of files following a standard layout. Helm reads this structure to know what to render, which values to use, what dependencies to pull in, and which files to ship when the chart is packaged.
mychart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration values
├── values.schema.json # Optional JSON schema for values validation
├── charts/ # Chart dependencies (managed by Helm)
├── templates/ # Kubernetes manifest templates
│ ├── NOTES.txt # Post-install notes shown to user
│ ├── _helpers.tpl # Reusable template helpers
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ └── serviceaccount.yaml
├── .helmignore # Files to ignore when packaging
└── README.md
Chart.yaml
Chart.yaml is the chart's metadata file — it declares the chart name, version, and type, and lists any dependencies (other charts to install alongside it). Its version field also drives Helm's upgrade and rollback logic.
apiVersion: v2
name: my-app
description: A Helm chart for Kubernetes
type: application # 'application' or 'library'
version: 1.2.0 # chart version (SemVer)
appVersion: "2.5.0" # application version
dependencies:
- name: postgresql
version: "15.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
tags:
- database
- name: redis
version: "19.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
Templating fundamentals
_helpers.tpl
Files prefixed with _ are never rendered as Kubernetes resources — they only hold reusable template definitions. _helpers.tpl is where you define the labels and selector labels that every other manifest includes, keeping them consistent across all resources in the chart.
{{/* Common labels */}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
Common template patterns
Templates use Go's text/template syntax plus Helm-specific functions. These are the patterns you'll see in almost every chart: conditionals, defaults, with to scope context, range to iterate, named-template includes, and tpl for values that are themselves templates.
# Conditionals
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name }}
{{- end }}
# Default values
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
# With block (scopes context)
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 10 }}
{{- end }}
# Ranges
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
# Named templates
{{- include "my-app.labels" . | nindent 4 }}
# Using tpl for dynamic values
value: {{ tpl .Values.dynamicValue . }}
Values injection (values.yaml)
values.yaml defines the chart's configurable settings and their defaults. Operators override these at install or upgrade time with --set or -f, so keep every tunable an operator might need — replicas, image, resources, feature toggles — here rather than hardcoded in templates.
replicaCount: 3
image:
repository: nginx
tag: "1.25"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: alb
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
postgresql:
enabled: true
auth:
username: app
database: appdb
Deployment template
This is a typical Deployment template in a chart. Note how every value comes from .Values, labels are injected via the _helpers.tpl includes, and YAML indentation is handled with nindent — so the rendered output is always valid, no matter how values are overridden.
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
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 }}"
ports:
- containerPort: {{ .Values.service.port }}
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
Hooks
Helm hooks run Kubernetes jobs at specific lifecycle points:
| Hook | Runs |
|---|---|
pre-install | Before resources are created |
post-install | After all resources are created |
pre-delete | Before resources are removed |
post-delete | After all resources are removed |
pre-upgrade | Before upgrade |
post-upgrade | After upgrade |
pre-rollback | Before rollback |
post-rollback | After rollback |
test | On helm test |
Database migration hook
Database migrations should run once per release, before new pods start serving traffic — not on every pod startup. This Job is annotated as a post-install,post-upgrade hook, so Helm runs it after resources are created and again on each upgrade, with a hook-weight ordering it and a delete policy that cleans it up once it succeeds.
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-migration"
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: "{{ .Values.image.repository }}-migrate:{{ .Values.image.tag }}"
env:
- name: DATABASE_URL
value: {{ .Values.databaseUrl }}
See also
- Kubernetes Configuration Reference — kubectl and cluster config
- Amazon EKS — AWS managed Kubernetes