Helm Helm

Lifecycle Hooks and Chart Tests

Run one-off work around an install or upgrade, and write a test that verifies a release actually works.

12 min Advanced

Most of what a chart does is declare resources that stay running. Sometimes you need something that runs once at a specific moment, a database migration before an upgrade, a cleanup job after an uninstall, or a check that the release actually works once it’s up. Helm covers both with hooks and chart tests.

What a hook is

A hook is an ordinary Kubernetes resource, most often a Job, annotated to tell Helm when to run it relative to an install, upgrade, rollback, or uninstall:

# templates/hooks/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Values.appName }}-pre-install
  annotations:
    "helm.sh/hook": 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: myorg/webapp-migrate:1.0
          command: ["./run-migrations.sh"]

The helm.sh/hook annotation is what makes this a hook rather than a normal resource Helm tracks as part of the release. The common hook points are pre-install, post-install, pre-upgrade, post-upgrade, pre-rollback, post-rollback, pre-delete, and post-delete; the full list, including a couple of test-related ones, is in the Helm hooks documentation.

How Helm sequences a hook

For a pre-install hook:

  1. Helm renders the hook resource and creates it in the cluster, ahead of anything else in the chart.
  2. Helm waits for it to reach a ready or completed state (a Job running to completion counts).
  3. Only then does Helm render and apply the rest of the chart’s resources.

post-install reverses step 1 and 3: the chart’s regular resources go in first, then the hook runs. A pre-upgrade hook that fails stops the upgrade, which is exactly the point of running a migration there rather than hoping it succeeds after the fact.

Hook weight and delete policy

helm.sh/hook-weight orders multiple hooks at the same point, lower numbers run first, as plain strings ("0", "5"), and hooks without a weight default to 0. helm.sh/hook-delete-policy controls cleanup, since a completed Job otherwise lingers in the cluster indefinitely:

  • before-hook-creation (the default): delete the previous hook resource before creating a new one, so retries don’t collide with a leftover Job of the same name.
  • hook-succeeded: delete the resource once it completes successfully.
  • hook-failed: delete it if it fails, useful when you’d rather clean up than leave a failed Job around for inspection; omit it if you want to kubectl logs a failure before it’s removed.

A pre-install,hook-succeeded combination, for instance, means the hook runs once, succeeds, and cleans itself up, leaving no trace beyond your Job’s own output; add hook-failed too if you don’t want a failed attempt hanging around either.

Try it

kubectl get jobs -w

In another terminal:

helm install web ./webapp

Watching the first terminal, you should see the hook Job appear, run, complete, and (depending on its delete policy) disappear, all before the chart’s Deployment shows up. This is the easiest way to actually see the sequencing rather than take it on faith.

Chart tests: verify a release after it’s up

A chart test is a Job that runs on demand, after a release exists, to check that it’s actually working:

# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ .Values.appName }}-test-connection"
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox:1.36
      command: ["wget", "-qO-", "{{ .Values.appName }}"]
  restartPolicy: Never
helm install web ./webapp
helm test web

helm test runs every resource annotated helm.sh/hook: test in the release and reports pass or fail based on each Pod’s exit status; a wget that gets a response exits 0, one that can’t connect doesn’t. Unlike install/upgrade hooks, test resources aren’t deleted automatically by default, run helm test --logs to see their output inline, or clean them up yourself between runs.

When to reach for each

  • Use a hook when something needs to happen exactly once, at a specific point in a release’s lifecycle, and the rest of the deploy should wait on or react to it: schema migrations before an upgrade, notifying an external system after install, cleaning up external resources before an uninstall.
  • Use a chart test for anything you want to re-run on demand against an already-running release, most often as a smoke test after helm install or helm upgrade in a CI pipeline, without it being part of the deploy sequence itself.

Both are ordinary Kubernetes resources underneath the annotations, so anything you already know about debugging a Job or a Pod, kubectl describe, kubectl logs, applies directly.

Popular Guides