A Helm hook (pre-install, post-upgrade, pre-delete) runs a Kubernetes resource at a precise moment in a release’s lifecycle, outside the normal tracking Helm does for that release. That distinction, invisible as long as everything goes well, becomes critical the day a helm rollback is supposed to undo everything and doesn’t.
A hook isn’t tracked like the rest of the release
A chart’s normal resources (Deployments, Services) get tracked by Helm in the release manifest: helm rollback compares the current state to an earlier revision and restores the differences. A resource marked as a hook gets treated differently: it runs at the indicated moment, then Helm stops tracking it as part of the current release.
# This Job runs before installation,
# but is never tracked as part of the release
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
"helm.sh/hook": pre-install
spec:
template:
spec:
containers:
- name: migrate
image: myapp-migrate
restartPolicy: Never
Why helm rollback never replays a hook
helm rollback restores a release’s normal resources to their earlier state, but never re-runs any hook associated with that earlier revision. A database migration run as pre-install during version 2’s deployment doesn’t run in reverse during a rollback to version 1: the rollback restores the application code, never the database state a hook had modified.
# Restores the Deployment to the previous version,
# but touches neither any hook nor what it modified
helm rollback myapp 3
This behavior isn’t a bug, it’s documented: hooks are designed for one-way actions (initializing a database, sending a notification), not for automatically reversible changes. A rollback that implicitly assumes a database migration will be undone alongside the code is systematically wrong.
The trap of a hook that fails silently in appearance
By default, a failing hook blocks the deployment (helm install or upgrade fails), but certain hook deletion policies (helm.sh/hook-delete-policy) can delete the resource even on failure, leaving little trace to diagnose afterward why the installation failed.
annotations:
"helm.sh/hook": pre-install
# Without this policy, the failed Job stays visible
# for diagnosis; with it, it vanishes after the attempt
"helm.sh/hook-delete-policy": hook-succeeded
Choosing before-hook-creation over hook-succeeded keeps a failed resource visible until the next attempt, a choice that matters concretely when diagnosing a deployment that fails with no actionable error message in Helm’s own logs.
What this means for a real rollback strategy
A reliable rollback for an application using migration hooks can’t rely on helm rollback alone: database migration needs its own reversibility strategy, generally via the expand/contract pattern that keeps every schema step backward-compatible, independent of what Helm does or doesn’t track.
Takeaway
A Helm hook runs outside the release’s normal lifecycle, which means helm rollback never replays it, in either direction: hooks are designed for one-way actions, not automatically reversible changes. The hook deletion policy determines whether a failed resource stays visible for diagnosis or vanishes silently, a detail to choose consciously rather than leave at the default. A reliable rollback strategy for an application with migration hooks needs to handle database reversibility separately, one of the details that matter the moment a CI/CD industrialization relies on Helm for genuinely reversible deployments.