Resource hooks allow you to run Kubernetes Jobs, Pods, or other resources at specific points during the sync lifecycle. They’re essential for tasks like database migrations, smoke tests, and notifications.
Available Hook Types
Hook When It Runs Use Case PreSyncBefore applying manifests Database migrations, pre-flight checks SyncWith manifests, after PreSync succeeds Additional resources that sync with the app SkipNever (resource is ignored) Disable specific resources PostSyncAfter sync succeeds and resources are healthy Smoke tests, notifications SyncFailWhen sync operation fails Cleanup, rollback, failure notifications PreDeleteBefore Application deletion Backup data, external cleanup PostDeleteAfter Application deletion Remove external resources, audit logs
Basic Hook Configuration
Add the hook annotation to any Kubernetes resource:
apiVersion : batch/v1
kind : Job
metadata :
name : my-hook
annotations :
argocd.argoproj.io/hook : PreSync
spec :
template :
spec :
containers :
- name : hook
image : my-image:latest
command : [ "do-something.sh" ]
restartPolicy : Never
Hooks can be any Kubernetes resource, but Jobs and Pods are most common.
PreSync Hooks
Run before the main sync operation. Ideal for migrations and prerequisites.
Database Migration Example
apiVersion : batch/v1
kind : Job
metadata :
name : db-migration
annotations :
argocd.argoproj.io/hook : PreSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
argocd.argoproj.io/sync-wave : "-1"
spec :
ttlSecondsAfterFinished : 3600
template :
spec :
containers :
- name : migrate
image : migrate/migrate:v4
env :
- name : DATABASE_URL
valueFrom :
secretKeyRef :
name : db-credentials
key : url
command :
- migrate
- "-path=/migrations"
- "-database=$(DATABASE_URL)"
- up
restartPolicy : Never
backoffLimit : 3
Pre-Flight Check Example
apiVersion : batch/v1
kind : Job
metadata :
name : preflight-check
annotations :
argocd.argoproj.io/hook : PreSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : check
image : busybox:1.36
command :
- sh
- -c
- |
# Check if database is ready
until nc -z postgres-service 5432; do
echo "Waiting for database..."
sleep 2
done
echo "Database is ready"
restartPolicy : Never
If a PreSync hook fails, the entire sync operation stops. Design hooks to be idempotent and have appropriate retry logic.
Sync Hooks
Run at the same time as regular manifests, after PreSync hooks succeed.
apiVersion : batch/v1
kind : Job
metadata :
name : sync-job
annotations :
argocd.argoproj.io/hook : Sync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : sync-task
image : my-sync-tool:latest
command : [ "sync-data.sh" ]
restartPolicy : Never
PostSync Hooks
Run after all resources are synced and healthy. Perfect for validation and notifications.
Smoke Test Example
apiVersion : batch/v1
kind : Job
metadata :
name : smoke-test
annotations :
argocd.argoproj.io/hook : PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : test
image : curlimages/curl:8.0.0
command :
- sh
- -c
- |
# Wait for service to be ready
sleep 10
# Test health endpoint
if curl --fail --silent http://my-app-service/health; then
echo "Health check passed"
exit 0
else
echo "Health check failed"
exit 1
fi
restartPolicy : Never
backoffLimit : 5
Slack Notification Example
apiVersion : batch/v1
kind : Job
metadata :
generateName : slack-notification-
annotations :
argocd.argoproj.io/hook : PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : notify
image : curlimages/curl:8.0.0
env :
- name : SLACK_WEBHOOK
valueFrom :
secretKeyRef :
name : slack-webhook
key : url
command :
- sh
- -c
- |
curl -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d '{
"channel": "#deployments",
"username": "ArgoCD",
"text": "✅ Application synced successfully",
"icon_emoji": ":rocket:"
}'
restartPolicy : Never
backoffLimit : 2
Data Seeding Example
apiVersion : batch/v1
kind : Job
metadata :
name : seed-data
annotations :
argocd.argoproj.io/hook : PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : seed
image : postgres:15
env :
- name : PGHOST
value : postgres-service
- name : PGDATABASE
value : myapp
- name : PGUSER
valueFrom :
secretKeyRef :
name : db-credentials
key : username
- name : PGPASSWORD
valueFrom :
secretKeyRef :
name : db-credentials
key : password
command :
- psql
- "-c"
- "INSERT INTO users (name, email) VALUES ('admin', '[email protected] ') ON CONFLICT DO NOTHING;"
restartPolicy : Never
SyncFail Hooks
Run when the sync operation fails. Useful for cleanup and notifications.
apiVersion : batch/v1
kind : Job
metadata :
name : sync-failure-alert
annotations :
argocd.argoproj.io/hook : SyncFail
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : alert
image : curlimages/curl:8.0.0
env :
- name : PAGERDUTY_KEY
valueFrom :
secretKeyRef :
name : pagerduty
key : api-key
command :
- sh
- -c
- |
curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d '{
"routing_key": "'$PAGERDUTY_KEY'",
"event_action": "trigger",
"payload": {
"summary": "ArgoCD sync failed",
"severity": "error",
"source": "argocd"
}
}'
restartPolicy : Never
SyncFail hooks run even if PreSync or Sync hooks fail. If SyncFail hooks fail, Argo CD marks the operation as failed but takes no special action.
PreDelete Hooks
Run before Application deletion. Only triggers on Application deletion, not during normal sync with pruning.
Database Backup Example
apiVersion : batch/v1
kind : Job
metadata :
name : backup-database
annotations :
argocd.argoproj.io/hook : PreDelete
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : backup
image : postgres:15
env :
- name : PGHOST
value : postgres-service
- name : BACKUP_BUCKET
value : s3://backups/myapp
command :
- sh
- -c
- |
pg_dump -Fc myapp > /tmp/backup.dump
aws s3 cp /tmp/backup.dump $BACKUP_BUCKET/backup-$(date +%Y%m%d-%H%M%S).dump
restartPolicy : Never
backoffLimit : 3
External Resource Cleanup
apiVersion : batch/v1
kind : Job
metadata :
name : cleanup-external-resources
annotations :
argocd.argoproj.io/hook : PreDelete
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : cleanup
image : amazon/aws-cli:2.13.0
env :
- name : APP_NAME
value : my-app
command :
- sh
- -c
- |
# Delete S3 buckets
aws s3 rb s3://$APP_NAME-storage --force
# Delete RDS instance
aws rds delete-db-instance \
--db-instance-identifier $APP_NAME-db \
--skip-final-snapshot
restartPolicy : Never
If a PreDelete hook fails, Application deletion is blocked. The Application enters a DeletionError state and resources remain in the cluster.
Recovering from failed PreDelete hooks:
Fix the hook in Git and Argo CD will retry on next reconciliation
Or manually delete the failing hook resource: kubectl delete job <hook-name>
PostDelete Hooks
Run after all Application resources are deleted. Available in Argo CD v2.10+.
Audit Log Example
apiVersion : batch/v1
kind : Job
metadata :
name : audit-deletion
annotations :
argocd.argoproj.io/hook : PostDelete
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
containers :
- name : audit
image : curlimages/curl:8.0.0
env :
- name : AUDIT_API
value : https://audit.example.com/api/events
command :
- sh
- -c
- |
curl -X POST "$AUDIT_API" \
-H 'Content-Type: application/json' \
-d '{
"event": "application_deleted",
"app": "my-app",
"timestamp": "'$(date -Iseconds)'"
}'
restartPolicy : Never
Skip Hook
Prevent resources from being applied:
apiVersion : v1
kind : Service
metadata :
name : temp-service
annotations :
argocd.argoproj.io/hook : Skip
spec :
# This service will never be applied
selector :
app : my-app
ports :
- port : 80
Useful for disabling Helm hooks that conflict with Argo CD, such as ingress-nginx admission webhooks.
Hook Deletion Policies
Control when hooks are cleaned up:
metadata :
annotations :
argocd.argoproj.io/hook : PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
Policy Behavior HookSucceededDelete after successful completion HookFailedDelete after failure BeforeHookCreationDelete existing hook before creating new one (default)
If no policy is specified, BeforeHookCreation is used by default.
Combining Deletion Policies
metadata :
annotations :
argocd.argoproj.io/hook : PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded,HookFailed
This deletes the hook whether it succeeds or fails.
Combining Hooks with Sync Waves
Use sync waves to order hooks within the same phase:
# Run first
apiVersion : batch/v1
kind : Job
metadata :
name : db-backup
annotations :
argocd.argoproj.io/hook : PreSync
argocd.argoproj.io/sync-wave : "-2"
argocd.argoproj.io/hook-delete-policy : HookSucceeded
# ... job spec
---
# Run second
apiVersion : batch/v1
kind : Job
metadata :
name : db-migration
annotations :
argocd.argoproj.io/hook : PreSync
argocd.argoproj.io/sync-wave : "-1"
argocd.argoproj.io/hook-delete-policy : HookSucceeded
# ... job spec
Multiple Hooks
A resource can have multiple hook types:
apiVersion : batch/v1
kind : Job
metadata :
name : multi-hook
annotations :
argocd.argoproj.io/hook : PreSync,PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
# This job runs both before and after sync
template :
spec :
containers :
- name : task
image : my-image:latest
restartPolicy : Never
Advanced Patterns
Conditional Hook with Init Container
apiVersion : batch/v1
kind : Job
metadata :
name : conditional-migration
annotations :
argocd.argoproj.io/hook : PreSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
initContainers :
- name : check-version
image : postgres:15
command :
- sh
- -c
- |
# Check if migration is needed
VERSION=$(psql -tAc "SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1")
if [ "$VERSION" -ge "20240101" ]; then
echo "Already migrated"
exit 0
fi
containers :
- name : migrate
image : migrate/migrate:v4
command : [ "migrate" , "up" ]
restartPolicy : Never
Hook with Service Account
For hooks that need Kubernetes API access:
apiVersion : v1
kind : ServiceAccount
metadata :
name : hook-sa
---
apiVersion : rbac.authorization.k8s.io/v1
kind : Role
metadata :
name : hook-role
rules :
- apiGroups : [ "" ]
resources : [ "pods" ]
verbs : [ "get" , "list" ]
---
apiVersion : rbac.authorization.k8s.io/v1
kind : RoleBinding
metadata :
name : hook-rb
roleRef :
apiGroup : rbac.authorization.k8s.io
kind : Role
name : hook-role
subjects :
- kind : ServiceAccount
name : hook-sa
---
apiVersion : batch/v1
kind : Job
metadata :
name : k8s-api-hook
annotations :
argocd.argoproj.io/hook : PostSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
template :
spec :
serviceAccountName : hook-sa
containers :
- name : kubectl
image : bitnami/kubectl:latest
command :
- kubectl
- get
- pods
restartPolicy : Never
Hook Best Practices
Idempotent Hooks Design hooks to be safely re-runnable in case of failures
Set Timeouts Use activeDeadlineSeconds to prevent hung jobs
Appropriate Backoff Set reasonable backoffLimit for retries
Clean Up Always use deletion policies to avoid resource accumulation
Recommended Job Configuration
apiVersion : batch/v1
kind : Job
metadata :
name : well-configured-hook
annotations :
argocd.argoproj.io/hook : PreSync
argocd.argoproj.io/hook-delete-policy : HookSucceeded
spec :
# Delete job 1 hour after completion
ttlSecondsAfterFinished : 3600
# Retry up to 3 times
backoffLimit : 3
template :
spec :
# Job timeout: 5 minutes
activeDeadlineSeconds : 300
containers :
- name : hook
image : my-image:latest
command : [ "do-task.sh" ]
# Container resource limits
resources :
requests :
memory : "128Mi"
cpu : "100m"
limits :
memory : "256Mi"
cpu : "200m"
restartPolicy : Never
Troubleshooting
Hook Not Running
Verify annotation syntax:
kubectl get job my-hook -o yaml | grep argocd
Check hook status in Argo CD UI or CLI:
argocd app get my-app --show-operation
Ensure hook is in the correct phase
Hook Fails Continuously
Check job logs:
Describe the job:
kubectl describe job my-hook
Verify environment variables and secrets
Sync Blocked by Failed Hook
# Manually delete the hook to unblock sync
kubectl delete job my-hook
# Or fix the hook in Git and Argo CD will retry
Next Steps
Sync Waves Combine hooks with waves for precise control
Sync Options Configure additional sync behavior
Health Checks Ensure resources are healthy
Creating Apps Learn how to create applications