Most charts deploy resources and stop there. But many real applications need work to happen around the deploy: run a schema migration, seed a default user, warm a cache, deregister from a load balancer. Helm's hook system covers these cases.
What Is a Hook?
A hook is a regular Kubernetes manifest (usually a Job) annotated to fire at a specific point in the release lifecycle:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./manage.py", "migrate"]
Available Hook Phases
| Hook | When it runs |
|---|---|
pre-install | After templates rendered, before any resource is created |
post-install | After all resources are loaded |
pre-delete | Before any resource is deleted on uninstall |
post-delete | After all resources are deleted |
pre-upgrade | Before applying upgrade manifests |
post-upgrade | After upgrade resources are loaded |
pre-rollback / post-rollback | Around rollback operations |
test | On demand via helm test <release> |
An annotation can list multiple hooks comma-separated: "helm.sh/hook": pre-install,pre-upgrade.
Hook Weight
If multiple hooks share a phase, helm.sh/hook-weight (a string!) controls order — lower runs first. Negative values are allowed:
"helm.sh/hook-weight": "-5" # creates the secret first
"helm.sh/hook-weight": "0" # then migrates
"helm.sh/hook-weight": "10" # then seeds
Hooks at the same weight have unspecified order. Helm waits for each hook resource to reach a "Ready" state (Job: completion; Pod: Ready condition) before moving to the next phase.
Deletion Policy
By default, hook resources remain in the cluster after the hook runs, accumulating with every release. Set a deletion policy:
| Policy | Effect |
|---|---|
before-hook-creation (default) | Delete any previous version of this hook before creating the new one |
hook-succeeded | Delete after the hook successfully completes |
hook-failed | Delete after failure (so retry is clean — but you lose logs!) |
Sensible combination for migrations:
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
Failed Jobs stay in the namespace so you can kubectl logs them.
Hooks Are Not Tracked as Release Resources
This is the trap that surprises everyone: a manifest annotated as a hook is removed from the release manifest. It is not managed across upgrades; it is fire-and-forget. helm rollback will not recreate or revert a hook's effect. If a hook ran a migration, rolling back the chart does not roll back the database.
Treat hooks as side-effects you accept responsibility for separately.
Common Patterns
Database migrations
A pre-upgrade Job that runs your migration tool. Combine with --atomic: if the migration fails, Helm rolls the manifest changes back — but you must verify the database is in a consistent state manually.
Seed data
A post-install Job that creates default users, demo data, or admin tokens. Make it idempotent — re-running should be safe.
Pre-delete cleanup
A pre-delete Job that drains queues, deregisters from external systems, or backs up data before uninstall.
CRD installation
For evolving CRDs (the crds/ folder only installs once), some operator charts use a pre-install,pre-upgrade hook to kubectl apply the CRDs explicitly. Riskier — you are bypassing Helm's tracking.
helm test
Special hook phase test runs only on demand:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test"
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": hook-succeeded
spec:
restartPolicy: Never
containers:
- name: curl
image: curlimages/curl:8.7.1
command: ["curl"]
args: ["-fsS", "http://{{ include "my-app.fullname" . }}/healthz"]
helm test my-app -n web
Use cases: post-deploy smoke tests in CI, scheduled production health checks, validation in upgrade pipelines.
Hook Pitfalls
- Idempotency: a hook may run more than once across retries. Migrations should be safe to re-run.
- Image pulls: on slow registries, the Job's image pull can exceed the install timeout. Pre-pull images or extend
--timeout. - RBAC: the hook's ServiceAccount needs the right permissions. Often missed; deploy fails mysteriously.
- Hook + GitOps: Argo CD has its own
argocd.argoproj.io/hookannotations; Helm hooks are usually translated, but verify in your tooling.
Use hooks for genuine lifecycle work. Resist the temptation to use them for routine resources — they are second-class citizens in the release model.