Hooks in templates
A hook is a Job- or Pod-shaped manifest that hull runs at a specific lifecycle
point. Hooks live in a package’s hooks/ directory and are rendered by the same
engine as templates/ — every ${...} expression and control-flow directive
works. What makes a file a hook is its location and its lifecycle phase, plus
optional $-prefixed directives that hull strips before applying.
For when each event fires and how ordering and delete policies play out at runtime, see the Hooks guide.
The phase comes from the filename
Name a hook file after its lifecycle event and hull picks the phase up from the name — no directive required:
hooks/pre-install.yaml
hooks/post-upgrade.yaml
hooks/pre-install-10.yaml # a numeric suffix is allowed
The valid phases are:
| Phase | Fires |
|---|---|
pre-install / post-install |
around install |
pre-upgrade / post-upgrade |
around upgrade |
pre-delete / post-delete |
around uninstall |
pre-rollback / post-rollback |
around rollback |
Each file targets one phase. To run the same work before install and
before upgrade, ship two files (or share the body through an $include
partial).
The $hook directive
Add a $hook directive only when you need to set weight, delete policy, or
timeout, or when the filename doesn’t convey the phase. Two forms:
Map form — everything in one place:
$hook:
phase: pre-install
weight: 5
deletePolicy: hook-succeeded
timeout: 5m
String form with flat siblings:
$hook: pre-install
$hookWeight: 5
$hookDeletePolicy: hook-succeeded
$hookTimeout: 5m
| Field | Type | Default | Meaning |
|---|---|---|---|
phase |
string | filename | one lifecycle event |
weight |
int | 0 |
order within a phase; lower runs first |
deletePolicy |
string | none | when to delete the hook resource |
timeout |
duration | 5m |
how long to wait for the hook |
deletePolicy takes one value (not a list):
before-hook-creation— delete a prior instance before creating this one.hook-succeeded— delete after the hook succeeds.hook-failed— delete after the hook fails.
With no policy set, hull keeps the hook resource. Directives are stripped before the manifest is applied.
A migration hook
hooks/post-install.yaml:
$hook: post-install
$hookWeight: 5
$hookDeletePolicy: hook-succeeded
apiVersion: batch/v1
kind: Job
metadata:
name: ${release.name}-migrate-${release.revision}
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: ${values.image.repository}:${values.image.tag}
command: ["/app/migrate", "--idempotent"]
Rendered body (--release-name hello, image: {repository: myapp, tag: 1.4.2}):
apiVersion: batch/v1
kind: Job
metadata:
name: hello-migrate-1
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: myapp:1.4.2
command: ["/app/migrate", "--idempotent"]
At install time hull renders the file, strips the directives, applies the Job,
waits for it to complete (or hit timeout), then deletes it per
hook-succeeded.
Multi-document hook files
A hook file can hold several YAML documents. Put the $hook directive on one
document; all documents in the file share the phase and run together.
$hook: pre-install
$hookDeletePolicy: hook-succeeded
apiVersion: v1
kind: ConfigMap
metadata:
name: ${release.name}-migrate-script
data:
migrate.sh: |
#!/bin/sh
psql -f /migrations/0001.sql
---
apiVersion: batch/v1
kind: Job
metadata:
name: ${release.name}-migrate
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: postgres:16
command: [/scripts/migrate.sh]
Rendering inside hooks
Hooks use the full engine — ${...}, $if, $each, $switch, and $include
all behave as in templates/:
$hook: pre-install
$hookWeight: 1
$hookDeletePolicy: hook-succeeded
apiVersion: batch/v1
kind: Job
metadata:
name: ${release.name}-precheck
labels:
$include: common.labels
spec:
template:
spec:
restartPolicy: Never
containers:
$each: ${values.precheck.containers}
$yield:
name: ${$item.name}
image: ${$item.image}
Tests
Tests are a separate mechanism from hooks. Test manifests live in the tests/
directory, are rendered and stored when you install or upgrade, and run only
when you invoke hull test <release> — never during install or upgrade. A test
is a plain Pod or Job manifest; no $hook directive is involved.
tests/connection.yaml:
apiVersion: v1
kind: Pod
metadata:
name: ${release.name}-connection-test
spec:
restartPolicy: Never
containers:
- name: probe
image: curlimages/curl:8.4.0
command:
- sh
- -c
- curl -fsS http://${release.name}.${release.namespace}.svc.cluster.local/health
hull test <release> applies each stored test, waits for its Pod/Job to finish,
and reports pass or fail from the exit status. Test resources are deleted after
each run. hull test exits non-zero if any test fails.
hull test my-app
hull test my-app --logs # print pod logs
hull test my-app --filter connection # run tests whose name contains "connection"
hull test my-app --parallel 4 # run up to 4 at once
hull test my-app -o junit # human | junit | json
Rollback re-runs a revision’s hooks
Hull stores each revision’s rendered hooks in that revision’s release record. Rolling back re-applies the old revision and re-runs its rollback hooks as they were when that revision was installed. Design hooks to be idempotent and self-contained — a rollback to revision 3 replays revision 3’s hooks, so they must not assume state that only a later revision created.
Inspecting hooks
hull get hooks <release> # rendered hook manifests + last results
hull get hooks <release> --revision 3
hull history <release> # per-revision hook outcomes
hull status <release> # current hook section
See also
- Hooks guide — event ordering, delete policies at runtime
- Control flow — the directives hook bodies use
- Expressions —
${...}inside hook manifests