How do Helm hooks work, and what's a scenario where a badly-designed hook can make an upgrade worse than having no hook at all?
Quick Answer
Hooks are Kubernetes resources (usually Jobs) annotated with helm.sh/hook (e.g. pre-install, pre-upgrade, post-upgrade) that Helm creates and waits on at specific points in the release lifecycle, separate from the main chart resources. A badly-designed hook — e.g. a pre-upgrade database migration Job with no idempotency and no rollback path — can leave the database in a state that matches neither the old nor the new application version if the main upgrade subsequently fails, which is strictly worse than a failed upgrade that never touched the database at all.
Detailed Answer
Hooks are declared via annotations like helm.sh/hook: pre-upgrade on a Job or other resource manifest inside the chart; Helm creates that resource at the appropriate lifecycle point (before templates are applied, for pre-* hooks) and by default waits for it to reach a ready/completed state before proceeding, deleting it afterward unless a hook-delete-policy says otherwise.
The failure mode interviewers probe for: imagine a pre-upgrade hook runs a non-idempotent, non-transactional schema migration (e.g. altering a column type and backfilling data) and it partially completes before something else in the upgrade fails — a pod fails readiness checks, a subsequent resource is rejected by an admission webhook, whatever. Helm's --atomic flag will roll back the Kubernetes resources it just applied, but it has no way to reverse the migration the hook already ran against the database, since that's an external side effect outside Kubernetes's control entirely. You're now left with a database schema that matches neither the old application version (which expects the pre-migration schema) nor the new one fully (since the migration itself may have only partially applied) — a worse state than if the upgrade had simply failed to apply any Kubernetes resources at all.
The mitigation is to design migration hooks to be idempotent and backward-compatible with the previous application version for at least one deploy cycle (the classic 'expand/contract' migration pattern: add new columns without removing old ones first, deploy code that can read both, migrate data, then remove old columns in a later release), so a hook re-run or an application rollback doesn't strand the system in an inconsistent state.
Code Example
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeededInterview Tip
Naming the 'expand/contract' migration pattern explicitly is the strongest signal here — it shows you've actually designed safe schema migrations around a hook-based deploy, not just used hooks for logging.