Helm Hook Types Decision Guide: Pre-Install, Post-Upgrade, and Test Hooks Compared
A decision guide comparing Helm hook types for release lifecycle tasks. Includes a usage table, trade-off analysis, and a pre-install migration Job example with validation steps.
27 Jul 2025, 18:28 UTC

The decision: run auxiliary logic at exact release lifecycle points without embedding it in application containers
You often need to perform database migrations, schema validation, or backups that must occur at a precise moment during a Helm release. Embedding this logic inside your application container can lead to race conditions, bloated images, or startup failures if the external service is not ready. Helm hooks let you schedule separate Kubernetes objects to run at defined phases--before install, after upgrade, before delete, and more--keeping your application images focused and your releases predictable.
Useful takeaway: Choose hook types based on whether the task must block the release (pre/post hooks) or merely verify state after deployment (test hooks). Always pair hooks with a helm.sh/hook-delete-policy to prevent completed Jobs from cluttering your namespace.
Hook types and typical use cases
| Hook type | Execution timing | Typical use case | Blocks release? |
|---|---|---|---|
| pre-install | Before install resources created | Init DB schema, validate secrets | Yes |
| post-install | After install resources created | Seed initial data, notify | Yes |
| pre-upgrade | Before existing resources updated | Database backup, drain connections | Yes |
| post-upgrade | After upgrade resources updated | Run schema migrations, warm cache | Yes |
| pre-delete | Before uninstall removes resources | Export final state, graceful shutdown | Yes |
| post-delete | After uninstall completes | Clean up external cloud resources | Yes |
| test | On demand via helm test | Smoke tests, conformance checks | No |
Trade-offs and constraints
For Job resources, Helm waits for the pod to reach a completed state. If a pre- or post-hook fails, Helm marks the release as failed and stops the process. This provides a safety gate but can lead to stuck releases if the hook depends on a flaky external service.
Unlike standard chart resources, hook objects are not automatically deleted after they run. Without a delete policy, completed Jobs will accumulate in the namespace, potentially hitting quota limits or cluttering kubectl get pods output.
If you have multiple hooks for the same phase, Helm executes them in alphabetical order by name. To force a specific sequence, use the helm.sh/hook-weight annotation (lower numbers run first).
Hooks run in the same namespace as the release. They cannot easily manage resources in other namespaces without high-privilege ServiceAccounts, which introduces security risks.
Implementation: Pre-install and pre-upgrade migration Job
The following Job runs a database migration script before the first installation and before every upgrade. It uses annotations to attach it to the release lifecycle and a delete policy to tidy up after completion.
apiVersion: batch/v1
kind: Job
metadata:
name: my-app-db-migrate
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: -5
helm.sh/hook-delete-policy: hook-succeeded
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: my-registry/migration:1.2
command: /bin/sh -c /app/migrate.sh
env:
- name: DB_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
Set helm.sh/hook-delete-policy to hook-succeeded so Helm removes the Job after it completes successfully. If the migration fails, the Job stays in Completed or Failed status, and the release is marked as failed, stopping further progression.
The ServiceAccount used by the Job must have rights to read the db-secret and connect to the database. In cluster-admin scoped releases, this is usually automatic; for namespace-limited releases, attach a ServiceAccount with appropriate rbac.authorization.k8s.io/v1 rules.
Place this manifest in charts/my-chart/templates/ or embed it via helm install -f values.yaml. The helm template command lets you preview it locally without deploying.
After helm install --debug --wait, the logs should show the Job created before the main application pods. The command blocks until the Job reaches a terminal state. A successful migration lets the release continue; a failed migration aborts it and reports FAILED in helm status.
If the migration depends on an external service that is temporarily unavailable, the release stalls. Set appropriate timeouts or retries inside the script, and always test in a non-production namespace first.
Validation and verification
To confirm the hook annotations and delete policy appear in the rendered manifest, run:
helm template my-release ./my-chart --namespace test-ns
Inspect the output for a Job resource with helm.sh/hook: pre-install,pre-upgrade and helm.sh/hook-delete-policy: hook-succeeded. If using a custom ServiceAccount, verify its name is referenced in the pod spec.
To verify execution flow in a test cluster, install with the --wait flag:
helm install my-release ./my-chart --namespace test-ns --debug --wait
Debug logs display the Job being created before the main application pods. The command blocks until the Job reaches a terminal state. A successful migration lets the release continue; a failed migration aborts it and reports FAILED in helm status.
After an upgrade, run:
helm upgrade my-release ./my-chart --namespace test-ns --atomic
Then check helm status my-release to confirm the revision includes the pre-upgrade hook execution and the release state reflects completion.
Limitations
Hooks are not a substitute for Init Containers. If a task must run every time a specific pod starts (rather than once per release), use an Init Container instead. Hooks also do not execute during helm upgrade --force in the same sequence as a standard upgrade, and they cannot modify the values of the release currently being deployed. Additionally, without a delete policy, completed Jobs accumulate in the namespace, potentially consuming quota or obscuring kubectl get pods output. Always pair hooks with helm.sh/hook-delete-policy: hook-succeeded or hook-failed as appropriate.
After a release cycle, run kubectl get jobs -n and confirm no completed Jobs older than the last release remain. If you see leftover Jobs, annotate them with helm.sh/hook-delete-policy or clean them manually.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.