ArgoCD PreSync and PostSync Hooks
Use ArgoCD PreSync hooks for database migrations and PostSync hooks for smoke tests, with SyncFail hooks for automated rollback and cleanup.
π‘ Quick Answer: Annotate Jobs with
argocd.argoproj.io/hook: PreSyncto run database migrations before deployment,PostSyncfor smoke tests after deployment, andSyncFailfor cleanup when a sync fails.
The Problem
Application deployments often need pre-flight and post-flight actions:
- Before deployment β run database migrations, validate configs, check prerequisites
- After deployment β run smoke tests, send notifications, warm caches
- On failure β clean up partial deployments, send alerts, trigger rollback
Kubernetes has no native concept of deployment hooks with these semantics. ArgoCD fills the gap.
The Solution
Step 1: PreSync Hook β Database Migration
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: myapp
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "-1"
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: myapp/api:v1.2.0
command: ["python", "manage.py", "migrate", "--noinput"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-db-credentials
key: urlStep 2: PostSync Hook β Smoke Tests
apiVersion: batch/v1
kind: Job
metadata:
name: smoke-test
namespace: myapp
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: smoke
image: curlimages/curl:8.10.0
command:
- /bin/sh
- -c
- |
echo "Running smoke tests..."
# Test health endpoint
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://myapp-api.myapp.svc:8080/health)
if [ "$HTTP_CODE" != "200" ]; then
echo "FAIL: health endpoint returned $HTTP_CODE"
exit 1
fi
echo "PASS: health endpoint OK"
# Test API endpoint
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://myapp-api.myapp.svc:8080/api/v1/status)
if [ "$HTTP_CODE" != "200" ]; then
echo "FAIL: API status returned $HTTP_CODE"
exit 1
fi
echo "PASS: API status OK"
echo "All smoke tests passed!"Step 3: SyncFail Hook β Failure Notification
apiVersion: batch/v1
kind: Job
metadata:
name: sync-failure-notify
namespace: myapp
annotations:
argocd.argoproj.io/hook: SyncFail
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: notify
image: curlimages/curl:8.10.0
command:
- /bin/sh
- -c
- |
curl -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d '{
"text": "π¨ ArgoCD sync FAILED for myapp! Check the ArgoCD dashboard.",
"channel": "#deployments"
}'
env:
- name: SLACK_WEBHOOK
valueFrom:
secretKeyRef:
name: slack-webhook
key: urlSync Lifecycle
flowchart TD
A[Sync Triggered] --> B[PreSync Phase]
B --> B1["Job: db-migrate"]
B1 -->|Success| C[Sync Phase]
B1 -->|Failure| F[SyncFail Phase]
C --> C1[Apply Manifests]
C1 -->|All Healthy| D[PostSync Phase]
C1 -->|Failure| F
D --> D1["Job: smoke-test"]
D1 -->|Success| E["β
Sync Complete"]
D1 -->|Failure| F
F --> F1["Job: sync-failure-notify"]
F1 --> G["β Sync Failed"]Hook Delete Policies
| Policy | Behavior |
|---|---|
BeforeHookCreation | Delete previous hook before creating new one (default) |
HookSucceeded | Delete hook after it succeeds |
HookFailed | Delete hook after it fails |
# Keep failed hooks for debugging, clean up successful ones
annotations:
argocd.argoproj.io/hook-delete-policy: HookSucceededStep 4: Combining Hooks with Sync Waves
# Wave -2: PreSync config validation
apiVersion: batch/v1
kind: Job
metadata:
name: validate-config
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/sync-wave: "-2"
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: validate
image: myapp/config-validator:latest
command: ["validate", "--strict"]
---
# Wave -1: PreSync database migration
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/sync-wave: "-1"
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: myapp/api:v1.2.0
command: ["python", "manage.py", "migrate"]Common Issues
Hook Job Never Completes
Hooks must reach a terminal state (Complete or Failed). Ensure:
restartPolicy: Never(notAlways)backoffLimitis set to prevent infinite retries- Container command actually exits
Hook Runs on Every Sync
This is expected. Use BeforeHookCreation to clean up old hook resources.
PreSync Hook Blocks Deployment
If a PreSync hook fails, the sync stops. Use backoffLimit and check Job logs:
argocd app get myapp --show-operation
kubectl logs job/db-migrate -n myappBest Practices
- Use PreSync for migrations β never deploy code that needs a schema change without migrating first
- Use PostSync for validation β smoke tests catch issues before users hit them
- Use SyncFail for alerts β automated notification prevents silent failures
- Set
backoffLimitβ prevent infinite retry loops - Use
BeforeHookCreationas default delete policy β prevents old Job conflicts - Keep hooks idempotent β they may run multiple times
Key Takeaways
- ArgoCD hooks run at specific sync lifecycle phases: PreSync, Sync, PostSync, SyncFail
- Hooks are typically Jobs but can be any Kubernetes resource
- Combine hooks with sync waves for fine-grained ordering within each phase
- Delete policies control hook cleanup β
BeforeHookCreationis the safest default

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
