Skip to content
7 min read·Lesson 6 of 8

Hooks, Tests, and Release Lifecycle

Lifecycle hooks (pre-install, post-upgrade, pre-delete), helm tests, hook weights and deletion policies, and patterns for migrations and seeding.

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

HookWhen it runs
pre-installAfter templates rendered, before any resource is created
post-installAfter all resources are loaded
pre-deleteBefore any resource is deleted on uninstall
post-deleteAfter all resources are deleted
pre-upgradeBefore applying upgrade manifests
post-upgradeAfter upgrade resources are loaded
pre-rollback / post-rollbackAround rollback operations
testOn 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:

PolicyEffect
before-hook-creation (default)Delete any previous version of this hook before creating the new one
hook-succeededDelete after the hook successfully completes
hook-failedDelete 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/hook annotations; 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.

Key Takeaways

  • Hooks are normal manifests with the helm.sh/hook annotation; Helm orders them by weight.
  • Common hooks: pre-install/upgrade for migrations, post-install for seeding, pre-delete for cleanup.
  • Use hook-delete-policy to avoid leaving stale Jobs in the namespace.
  • helm test runs hook-test manifests against the release; ideal for smoke checks.
  • Hook failures stop the install/upgrade — combine with --atomic for safe rollback.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →