Skip to main content

Hooks and Tests


1 - Introduction to Hooks

Hooks let you execute actions at specific moments in a release's lifecycle.


2 - Hook types

HookExecution moment
pre-installBefore installing the resources
post-installAfter the installation
pre-deleteBefore deletion
post-deleteAfter deletion
pre-upgradeBefore an upgrade
post-upgradeAfter an upgrade
pre-rollbackBefore a rollback
post-rollbackAfter a rollback
testDuring helm test

3 - Create a Hook

3.1 Basic structure

A hook is a standard Kubernetes template with special annotations.

# templates/hooks/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pre-install"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
# Définir le type de hook
"helm.sh/hook": pre-install
# Ordre d'exécution (si plusieurs hooks)
"helm.sh/hook-weight": "5"
# Politique de suppression
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: pre-install
image: busybox
command: ['sh', '-c', 'echo Pre-install hook running']
restartPolicy: Never
backoffLimit: 1

3.2 Hook annotations

annotations:
# Type de hook (peut être multiple, séparé par virgules)
"helm.sh/hook": pre-install,pre-upgrade

# Poids (ordre d'exécution, plus bas = premier)
"helm.sh/hook-weight": "-5" # Exécuté avant les autres
"helm.sh/hook-weight": "0" # Par défaut
"helm.sh/hook-weight": "10" # Exécuté après

# Politique de suppression
"helm.sh/hook-delete-policy": before-hook-creation
"helm.sh/hook-delete-policy": hook-succeeded
"helm.sh/hook-delete-policy": hook-failed

3.3 Deletion policies

PolicyDescription
before-hook-creationDeletes the old hook before creating the new one
hook-succeededDeletes the hook on success
hook-failedDeletes the hook on failure

4 - Common use cases

4.1 Database migration

# templates/hooks/db-migrate.yaml
{{- if .Values.migrations.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-db-migrate"
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate.sh"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db-secret
key: url
restartPolicy: Never
backoffLimit: 3
{{- end }}

4.2 Backup before upgrade

# templates/hooks/pre-upgrade-backup.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-backup"
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: backup
image: postgres:15
command:
- /bin/bash
- -c
- |
BACKUP_FILE="/backups/backup-$(date +%Y%m%d-%H%M%S).sql"
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > $BACKUP_FILE
echo "Backup created: $BACKUP_FILE"
env:
- name: DB_HOST
value: "{{ .Values.postgresql.host }}"
- name: DB_USER
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db
key: password
volumeMounts:
- name: backups
mountPath: /backups
volumes:
- name: backups
persistentVolumeClaim:
claimName: {{ .Release.Name }}-backups
restartPolicy: Never

4.3 Post-delete cleanup

# templates/hooks/post-delete-cleanup.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-cleanup"
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
spec:
template:
spec:
serviceAccountName: {{ .Release.Name }}-cleanup-sa
containers:
- name: cleanup
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
# Supprimer les PVCs orphelins
kubectl delete pvc -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
# Supprimer les secrets générés
kubectl delete secret -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
restartPolicy: Never

4.4 Slack notification

# templates/hooks/notify-slack.yaml
{{- if .Values.notifications.slack.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-notify"
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "100"
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
spec:
template:
spec:
containers:
- name: notify
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"✅ Release {{ .Release.Name }} deployed successfully ({{ .Chart.Version }})"}' \
{{ .Values.notifications.slack.webhookUrl }}
restartPolicy: Never
{{- end }}

5 - Helm Tests

5.1 Introduction to tests

Helm tests verify that the release works correctly after deployment.

# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-test-connection"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

5.2 Run the tests

# Exécuter les tests
helm test my-release

# Avec timeout
helm test my-release --timeout 5m

# Voir les logs des tests
helm test my-release --logs

5.3 Test examples

HTTP test:

# templates/tests/test-http.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-http"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
# Test endpoint principal
curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/

# Test health endpoint
curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health

# Test avec assertion
RESPONSE=$(curl -s http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/api/version)
echo "Response: $RESPONSE"
echo "$RESPONSE" | grep -q "{{ .Chart.AppVersion }}"
restartPolicy: Never

Database test:

# templates/tests/test-db.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-db"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: postgres
image: postgres:15
command:
- /bin/sh
- -c
- |
pg_isready -h {{ .Values.postgresql.host }} -p 5432 -U {{ .Values.postgresql.username }}
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-db
key: password
restartPolicy: Never

Redis test:

# templates/tests/test-redis.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-redis"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: redis
image: redis:7
command:
- /bin/sh
- -c
- |
redis-cli -h {{ .Release.Name }}-redis ping | grep -q PONG
restartPolicy: Never

5.4 Multi-step tests

# templates/tests/test-integration.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ .Release.Name }}-test-integration"
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "10"
spec:
containers:
- name: test
image: python:3.11-slim
command:
- /bin/bash
- -c
- |
pip install requests pytest

cat > /tmp/test_app.py << 'EOF'
import requests
import pytest

BASE_URL = "http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"

def test_health():
response = requests.get(f"{BASE_URL}/health")
assert response.status_code == 200
assert response.json()["status"] == "healthy"

def test_api_version():
response = requests.get(f"{BASE_URL}/api/version")
assert response.status_code == 200
assert "{{ .Chart.AppVersion }}" in response.text

def test_create_resource():
response = requests.post(f"{BASE_URL}/api/items", json={"name": "test"})
assert response.status_code == 201

if __name__ == "__main__":
pytest.main([__file__, "-v"])
EOF

python /tmp/test_app.py
restartPolicy: Never

6 - Best practices

6.1 Organizing hooks

templates/
├── hooks/
│ ├── pre-install/
│ │ ├── create-namespace.yaml
│ │ └── init-secrets.yaml
│ ├── pre-upgrade/
│ │ ├── backup.yaml
│ │ └── db-migrate.yaml
│ ├── post-install/
│ │ └── notify.yaml
│ └── post-delete/
│ └── cleanup.yaml
└── tests/
├── test-connection.yaml
├── test-db.yaml
└── test-integration.yaml

6.2 Error handling

# Hook avec gestion d'erreur
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-migrate"
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-delete-policy": before-hook-creation
spec:
# Timeout du Job
activeDeadlineSeconds: 300
# Nombre de tentatives
backoffLimit: 3
template:
spec:
containers:
- name: migrate
image: myapp:{{ .Values.image.tag }}
command:
- /bin/bash
- -c
- |
set -e # Exit on error

echo "Starting migration..."
if ! ./migrate.sh; then
echo "Migration failed!"
exit 1
fi

echo "Migration completed successfully"
restartPolicy: Never

6.3 Timeout and resources

apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-hook"
annotations:
"helm.sh/hook": pre-install
spec:
activeDeadlineSeconds: 600
backoffLimit: 2
template:
spec:
containers:
- name: hook
image: myimage
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
restartPolicy: Never

Summary

In this chapter, we learned:

  • The different hook types and their execution moment
  • How to create hooks with the appropriate annotations
  • Common use cases (migrations, backups, notifications)
  • Creating and running Helm tests
  • Best practices for hooks and tests

Next step

In the next chapter, we will look at the overall Best practices for Helm.

→ Next chapter: Best practices


← Back to the table of contents