Run a One‑Time Initialization Job with Helm Post‑Install Hooks
Learn how to add a Helm post‑install hook that runs a one‑time initialization job, verifies its success, and recovers from failures.
04 Sept 2026, 18:33 UTC

Desired outcome
After a Helm chart is installed, a short‑lived job (for example, a database migration or a ConfigMap population) runs exactly once and Helm considers the release successful only when that job finishes without errors.
Prerequisites
- Helm v3 installed locally (
helm versionshows v3.x). kubectlconfigured to talk to the target cluster and you have permission to create Jobs, Pods, and ConfigMaps in the target namespace.- A Helm chart source directory that you can edit (we’ll refer to it as
./mychart).
Procedure
1. Add a post‑install hook template
Create a file ./mychart/templates/post-install-job.yaml with the following content. The hook runs a Job that executes a container image myorg/init-tool:1.0; replace the image and command with whatever initialization you need.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-post-install
annotations:
"helm.sh/hook": post-install
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
spec:
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Release.Name }}-post-install
spec:
restartPolicy: OnFailure
containers:
- name: init
image: myorg/init-tool:1.0
command: ["/bin/sh", "-c", "echo Running initialization… && ./run-migration.sh"]
# Add env vars, volume mounts, etc. as required
The annotation helm.sh/hook: post-install tells Helm to execute this Job after all regular resources are created. The delete policy ensures the Job pod is removed whether it succeeds or fails, preventing stale pods from accumulating.
2. Install the release and wait for the hook
Run the install command from your local workstation where you have Helm and kubectl access:
helm install my-release ./mychart \
--namespace my-namespace \
--create-namespace \
--wait \
--timeout 5m
--wait makes Helm block until all resources, including the hook Job, reach a ready state. --timeout caps the wait; adjust as needed for your initialization duration.
3. Verify the hook completed successfully
After the command returns, check the release status:
helm status my-release -n my-namespace
Look for a line like STATUS: deployed and ensure no error messages reference the hook.
Next, confirm the Job pod finished:
kubectl get jobs -n my-namespace -l helm.sh/hook=post-install
kubectl get pods -n my-namespace -l helm.sh/hook=post-install -o wide
The Job should show COMPLETIONS: 1/1 and the associated Pod STATUS: Completed with RESTARTS: 0. You can inspect the logs for details:
kubectl logs job/my-release-post-install -n my-namespace
If your initialization creates a ConfigMap or runs a database migration, verify that side effect (e.g., kubectl get configmap my-init-config -n my-namespace or a database query) to be certain the hook performed its work.
Expected checks
- Helm reports
LAST DEPLOYEDtimestamp and no hook‑related errors inhelm statusoutput. - The hook Job pod is
Completedwith zero restarts. - Logs show the initialization script ran to completion without error.
- Any intended artifact (ConfigMap, DB schema change, etc.) is present.
Recovery options
If the hook fails
Helm will mark the release as failed. You can:
- Fix the hook template (e.g., correct the image, command, or missing config).
- Re‑run the install with the same flags:
helm upgrade --install my-release ./mychart \
--namespace my-namespace \
--create-namespace \
--wait \
--timeout 5m
Because the delete policy removed the failed Job pod, Helm will create a fresh one on the next attempt.
Manual re‑invocation (optional)
If the chart also defines a test pod that validates the hook’s side effects, you can trigger it after fixing the hook:
helm test my-release -n my-namespace
This runs any helm.sh/hook: test resources and reports success or failure.
Limitations and practical verification
- The hook runs in the same namespace as the release; ensure it does not consume excessive resources or modify objects shared by other workloads.
- If you change the delete policy to retain the pod, you must clean it up manually after successful runs.
- To verify that the hook ran exactly once, check that no duplicate Job objects with the same
controller-uidexist:
kubectl get jobs -n my-namespace -l job-name=my-release-post-install -o jsonpath='{.items[*].metadata.uid}'
If you see more than one UID, the hook was re‑executed unintentionally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.