Orchestrate lifecycle actions and validate releases with Helm hooks and tests.
helm test: validating a live releaseHooks let you intervene at specific points during a release lifecycle:
| Hook | When It Runs |
|---|---|
pre-install | After templates render, before any resources are created |
post-install | After all resources are loaded into Kubernetes |
pre-upgrade | After templates render, before any resources are updated |
post-upgrade | After all resources are updated |
pre-delete | Before any resources are deleted from Kubernetes |
post-delete | After all resources are deleted |
pre-rollback | Before any resources are rolled back |
post-rollback | After all resources are rolled back |
Three annotations control hook behaviour:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate"]
helm.sh/hook — which lifecycle event(s) to attach to (comma-separated for multiple)helm.sh/hook-weight — integer that controls execution order (lower runs first)helm.sh/hook-delete-policy — when to delete the hook resourceHooks execute in ascending weight order. Hooks with the same weight run in name order.
# Runs first (weight -10)
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-10"
# Runs second (weight 0, default)
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "0"
# Runs third (weight 5)
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "5"
| Policy | Behaviour |
|---|---|
before-hook-creation | Delete previous hook resource before launching new one |
hook-succeeded | Delete after hook succeeds |
hook-failed | Delete if the hook fails |
before-hook-creation is the most common choice — it prevents "already exists" errors on repeated upgrades.apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-backup
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: backup
image: postgres:16
command:
- pg_dump
- -h
- {{ .Values.db.host }}
- -U
- {{ .Values.db.user }}
- -f
- /backups/pre-upgrade.sql
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-notify
annotations:
"helm.sh/hook": post-upgrade
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: notify
image: curlimages/curl:8.5.0
command:
- curl
- -X
- POST
- -H
- "Content-type: application/json"
- -d
- '{"text":"Release {{ .Release.Name }} upgraded to {{ .Chart.Version }}"}'
- {{ .Values.slack.webhookUrl }}
helm testA test is simply a pod with the helm.sh/hook: test annotation. Helm considers the test passed if the pod exits with code 0.
apiVersion: v1
kind: Pod
metadata:
name: {{ include "mychart.fullname" . }}-test-connection
labels:
{{- include "mychart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
containers:
- name: curl-test
image: curlimages/curl:8.5.0
command:
- curl
- --fail
- --silent
- --show-error
- http://{{ include "mychart.fullname" . }}:{{ .Values.service.port }}/healthz
# Run tests against a release
helm test my-release
# Run with increased timeout
helm test my-release --timeout 5m0s
# View test logs on failure
helm test my-release --logs
apiVersion: v1
kind: Pod
metadata:
name: {{ include "mychart.fullname" . }}-test-dns
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
containers:
- name: dns-check
image: busybox:1.36
command:
- nslookup
- {{ include "mychart.fullname" . }}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "mychart.fullname" . }}-test-data
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
containers:
- name: data-check
image: postgres:16
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ include "mychart.fullname" . }}
key: postgres-password
command:
- psql
- -h
- {{ include "mychart.fullname" . }}
- -U
- postgres
- -c
- "SELECT 1;"
hook-weight on tests to control order: check DNS first (weight 0), then HTTP health (weight 5), then data integrity (weight 10).before-hook-creation, hook-succeeded, hook-failed)helm.sh/hook: testbefore-hook-creation delete policy to avoid conflictsNext: Lesson 16 →