Hooks & Tests
Hooks & Tests
A Helm release is more than a batch of applied YAML — it is a lifecycle event. Database schemas must be migrated before new pods start. Secrets must be seeded into a vault before the application boots. Smoke tests must pass after an upgrade before traffic is re-routed. Helm exposes all of these integration points through two mechanisms: lifecycle hooks and helm test. Understanding both is what separates teams that confidently ship to production from teams that script around Helm's edges with brittle shell wrappers.
How Hooks Work
A hook is a standard Kubernetes manifest — most often a Job — that carries the annotation helm.sh/hook. Helm intercepts the manifest before the normal apply phase, executes it at the annotated lifecycle point, waits for it to complete, and then continues. The hook manifest is not tracked as a regular release resource; it has its own deletion policy controlled by helm.sh/hook-delete-policy.
The most important hooks in day-to-day production use:
pre-install— runs after templates are rendered, before any release resources are created. Use for: initialising a database schema, seeding an admin user, creating a namespace-scoped RBAC binding that the app depends on.post-install— runs after all release resources are created and ready. Use for: sending a Slack notification, populating a cache with warm data, registering the service in an external service registry.pre-upgrade— runs before the upgrade apply. Use for: running database migrations (the most common production use), taking a pre-upgrade snapshot of a stateful resource, draining a queue before the consumer pod is replaced.post-upgrade— runs after upgrade is complete. Use for: running smoke tests inline, invalidating a CDN cache, triggering a downstream pipeline.pre-delete— runs before release resources are deleted. Use for: gracefully terminating long-running workers, archiving audit logs, revoking service credentials.pre-rollback/post-rollback— mirror of the upgrade hooks, rarely needed but valuable for stateful apps that require schema downgrade scripts.
Writing a pre-upgrade Migration Hook
The most production-critical hook pattern is a database migration Job that runs before new application pods are scheduled. Here is a production-grade template:
.Release.Revision to the Job name? Kubernetes Job names must be unique. Without the revision suffix, the second upgrade attempt fails immediately because the Job from the first attempt still exists (or is in a failed state). Always include the revision or a timestamp in hook Job names.
Hook Weights and Ordering
When a release has multiple hooks of the same type, Helm orders them by the integer value of helm.sh/hook-weight — lowest integer first. Hooks with the same weight execute in alphabetical order by resource name. This matters when you need: (1) create a temporary admin DB user before running migrations, then (2) run migrations, then (3) revoke the admin user — three separate hooks with weights -10, 0, and 10.
Hook Delete Policies
Three values control when Helm deletes a hook resource after it runs:
hook-succeeded— delete the Job only if it completes successfully. Failed Jobs are preserved so you can inspect logs. This is the recommended default.hook-failed— delete even on failure. Use when hook resources contain sensitive data (migration credentials) that must not persist.before-hook-creation— delete any previous hook resource of the same name before creating a new one. This solves the duplicate-name problem for teams that prefer a fixed Job name.
helm.sh/hook-delete-policy, Helm uses before-hook-creation by default. That means a failed migration Job is deleted silently on the next deploy attempt, taking its logs with it. Always set hook-succeeded explicitly so failed Jobs stick around for debugging, and drain logs to your centralised log aggregator (Loki, CloudWatch, Datadog) so they survive Pod deletion.
helm test — Post-Deploy Smoke Tests
helm test is a first-class command that runs a set of special hook Pods annotated with "helm.sh/hook": test. These are not unit tests — they are release validation tests that run against the live deployed release to verify it is actually working. Think of them as automated smoke tests that anyone can trigger: helm test <release-name>.
A canonical test Pod checks the HTTP health endpoint of the deployed service:
Testing Patterns at Scale
A production-grade test suite in a Helm chart covers more than a single HTTP check. Organise multiple test Pods by concern, each annotated with "helm.sh/hook": test:
- Connectivity test — can the service resolve DNS and reach its database? Use
psql -c "\l"orredis-cli PINGfrom a sidecar image. - Auth test — does the API return 401 on unauthenticated requests and 200 with a valid token? A
curl-based Pod with an injected test credential (from aSecret) exercises this path. - Data integrity test — after a migration, does querying a known row return the expected schema? A migration hook that writes a
_schema_versionsentinel row, paired with a test Pod that reads it, gives you schema/app version alignment verification on every deploy.
helm upgrade --install --wait --atomic to a staging namespace → helm test (10–30 seconds) → gate production promotion on test exit code. If helm test fails, the pipeline aborts before the production upgrade even starts. This costs 30 seconds per deploy and has caught hundreds of regressions that would otherwise have reached production.
CRD Hooks and the Special pre-install Pattern
One of the most common reasons charts fail on fresh installs is that a Custom Resource Definition (CRD) is applied after the resources that depend on it. Helm has a dedicated crds/ directory that auto-installs CRDs before everything else, but when you are composing third-party CRDs as a dependency, a pre-install hook Job can apply the CRD manifest explicitly and wait for the API server to register it before proceeding:
Debugging Hooks
When a hook fails, Helm marks the release as failed and (with --atomic) triggers a rollback. The hook Job remains in the namespace (if you used hook-succeeded as the delete policy). Inspect it like any other failed Job:
Hooks and tests transform a Helm release from a static manifest apply into a choreographed, self-validating deployment pipeline. The next lesson — lesson 8 — covers publishing and versioning your charts so that other teams can consume them reliably from a registry.