Skip to main content
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

HookWhen It RunsUse Case
PreSyncBefore applying manifestsDatabase migrations, pre-flight checks
SyncWith manifests, after PreSync succeedsAdditional resources that sync with the app
SkipNever (resource is ignored)Disable specific resources
PostSyncAfter sync succeeds and resources are healthySmoke tests, notifications
SyncFailWhen sync operation failsCleanup, rollback, failure notifications
PreDeleteBefore Application deletionBackup data, external cleanup
PostDeleteAfter Application deletionRemove 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', 'admin@example.com') 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:
  1. Fix the hook in Git and Argo CD will retry on next reconciliation
  2. 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
PolicyBehavior
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
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

  1. Verify annotation syntax:
    kubectl get job my-hook -o yaml | grep argocd
    
  2. Check hook status in Argo CD UI or CLI:
    argocd app get my-app --show-operation
    
  3. Ensure hook is in the correct phase

Hook Fails Continuously

  1. Check job logs:
    kubectl logs job/my-hook
    
  2. Describe the job:
    kubectl describe job my-hook
    
  3. 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