TL;DR

Hooks run Kubernetes resources at specific lifecycle moments — migrations, smoke tests, cleanup. Library charts share helper templates across charts. Both are powerful but can surprise rollbacks and GitOps controllers if not designed carefully.

Hook Types

HookWhen it runsCommon use
pre-installBefore resources are installedPrepare external dependency
post-installAfter install completesOne-time setup or notification
pre-upgradeBefore upgrade appliesDB migration checks
post-upgradeAfter upgrade completesValidation Job
pre-deleteBefore uninstallBackup or graceful drain
post-deleteAfter uninstallExternal resource cleanup
testOn helm test onlySmoke test Pod

Hook Annotations

AnnotationPurpose
helm.sh/hookWhich lifecycle event(s) trigger this resource
helm.sh/hook-weightExecution order — lower numbers run first
helm.sh/hook-delete-policyWhen to delete the hook resource after it runs

Migration Hook Job

This hook runs a Kubernetes Job before an upgrade, often for database migration checks or one-time setup. Use hooks sparingly, because failed hooks can block or wedge a release.

yaml templates/migrate-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ include "web-api.fullname" . }}-migrate"
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  backoffLimit: 1
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command: ["./bin/migrate"]
          envFrom:
            - secretRef:
                name: "{{ include "web-api.fullname" . }}-db"

Helm Test Hook

A test hook runs only when you execute helm test. Use it for lightweight smoke checks that verify the release works after install or upgrade without becoming part of the normal workload.

yaml templates/tests/smoke.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "web-api.fullname" . }}-smoke"
  annotations:
    "helm.sh/hook": test
spec:
  restartPolicy: Never
  containers:
    - name: curl
      image: curlimages/curl:8.8.0
      command:
        - sh
        - -c
        - curl -fsS "http://{{ include "web-api.fullname" . }}:{{ .Values.service.port }}/health"

Library Charts

Library charts (type: library) provide shared templates but install no resources themselves. Parent charts import helpers via dependencies.

yaml common/Chart.yaml
apiVersion: v2
name: common
type: library
version: 1.0.0

This helper template lives in the library chart and can be reused by parent charts. Use it to standardize labels, names, or common snippets across many internal app charts.

yaml common/templates/_labels.tpl
{{- define "common.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end -}}

In the parent chart's Chart.yaml, add common as a dependency, then call {{ include "common.labels" . }} in templates.

Commands

Use these commands to inspect hook output, run Helm tests, and see which hook resources Helm created. They are your first stop when a hook blocks an install or upgrade.

bash hooks.sh
helm upgrade --install web-api ./charts/web-api -n app --wait --timeout 10m
helm test web-api -n app --logs
helm get hooks web-api -n app

# Debug a failed hook.
kubectl get jobs,pods -n app | grep -E 'migrate|hook'
kubectl logs job/web-api-migrate -n app
kubectl describe job/web-api-migrate -n app

Failed Hook Recovery

Use this during an incident where a hook Job failed or timed out. Inspect logs first, then delete only the failed hook resource if you are sure it is safe to retry or roll back.

bash hook-recovery.sh
# Release stuck in pending-upgrade because a hook Job failed.
helm status web-api -n app
kubectl logs job/web-api-migrate -n app

# Delete the failed hook Job so Helm can recreate it on retry.
kubectl delete job web-api-migrate -n app

# Retry the upgrade or rollback to last good revision.
helm rollback web-api -n app
helm upgrade web-api ./charts/web-api -n app -f values-prod.yaml --wait
⚠️
GitOps warning ArgoCD does not execute Helm hooks the same way as helm upgrade. Confirm the client's GitOps tool supports hooks before relying on pre-upgrade migration Jobs in an Argo-managed release.

Gotchas

  • !Hooks are not normal release resources — they may not appear in helm get manifest.
  • !Make migration hooks idempotent. Retried Jobs must not corrupt data.
  • !Use hook-delete-policy: before-hook-creation,hook-succeeded so old Jobs do not block future hook runs.
  • !Library charts never install resources — only parent/application charts create Kubernetes objects.
  • !Hook failures leave the release in pending-upgrade — check Job logs before retrying.