ArgoCD Sync Waves, Resource Hooks, and Sync Options#
ArgoCD sync is not just “apply all manifests at once.” You can control the order resources are created, run pre- and post-deployment tasks, restrict when syncs can happen, and tune how resources are applied. This is where ArgoCD moves from basic GitOps to production-grade deployment orchestration.
Sync Waves#
Sync waves control the order in which resources are applied. Every resource has a wave number (default 0). Resources in lower waves are applied and must be healthy before higher waves begin.
apiVersion: v1
kind: Namespace
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
annotations:
argocd.argoproj.io/sync-wave: "0"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "1"
---
apiVersion: v1
kind: Service
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "2"This creates the Namespace first (wave -1), then the ConfigMap (wave 0), then the Deployment (wave 1), and finally the Service (wave 2). Each wave waits for the previous wave’s resources to be healthy before proceeding.
Negative wave numbers are valid and useful for resources that must exist before everything else.
Resource Hooks#
Hooks are resources that run at specific points during the sync lifecycle. They are defined by the argocd.argoproj.io/hook annotation.
Hook Phases#
- PreSync – Runs before any resources are synced. Use for database migrations, schema changes, pre-flight checks.
- Sync – Runs alongside normal resource sync. Rarely used directly.
- PostSync – Runs after all resources are synced and healthy. Use for smoke tests, notifications, cache warming.
- SyncFail – Runs only if the sync fails. Use for rollback logic, alerting, cleanup.
- Skip – The resource is ignored by ArgoCD entirely.
Database Migration with PreSync Hook#
This is the most common hook use case. Run migrations before the new application version starts:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "-1"
spec:
template:
spec:
containers:
- name: migrate
image: myorg/my-app:v1.2.0
command: ["./migrate", "--direction", "up"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
restartPolicy: Never
backoffLimit: 1The hook-delete-policy: BeforeHookCreation deletes the previous Job before creating a new one on the next sync. Without this, Kubernetes rejects the Job creation because a Job with that name already exists. Other delete policies:
- HookSucceeded – Delete after successful completion.
- HookFailed – Delete after failure (useful to retry).
- BeforeHookCreation – Delete the old resource before creating a new one. This is the most commonly used policy.
PostSync Smoke Test#
apiVersion: batch/v1
kind: Job
metadata:
name: smoke-test
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
template:
spec:
containers:
- name: test
image: curlimages/curl:latest
command: ["curl", "--fail", "http://my-app.my-namespace.svc:8080/healthz"]
restartPolicy: Never
backoffLimit: 0SyncFail Notification#
apiVersion: batch/v1
kind: Job
metadata:
name: notify-failure
annotations:
argocd.argoproj.io/hook: SyncFail
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
template:
spec:
containers:
- name: notify
image: curlimages/curl:latest
command: ["curl", "-X", "POST", "https://hooks.slack.com/services/xxx",
"-d", "{\"text\":\"ArgoCD sync failed for my-app\"}"]
restartPolicy: NeverCombining Waves and Hooks#
Hooks respect sync waves. A PreSync hook at wave -2 runs before a PreSync hook at wave -1. This lets you order multiple pre-deployment steps:
Wave -2 (PreSync): Create temporary credentials
Wave -1 (PreSync): Run database migration
Wave 0 (Sync): Apply ConfigMaps and Secrets
Wave 1 (Sync): Apply Deployments
Wave 2 (PostSync): Run smoke testsSync Windows#
Sync windows restrict when ArgoCD can sync applications. This is critical for production environments where deployments should only happen during business hours or maintenance windows.
Sync windows are configured on AppProjects:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
spec:
syncWindows:
- kind: allow
schedule: '0 9 * * 1-5' # Weekdays 9 AM UTC
duration: 8h
applications: ['*']
- kind: deny
schedule: '0 0 25 12 *' # Christmas Day
duration: 24h
applications: ['*']
- kind: allow
schedule: '0 0 * * *' # Always allow
duration: 24h
applications: ['critical-hotfix-*']
manualSync: trueThe kind: allow windows define when syncs are permitted. The kind: deny windows define blackout periods. Manual syncs can bypass windows if manualSync: true is set.
Sync Options#
Sync options modify how ArgoCD applies resources. Set them per-application or per-resource.
Per-Application#
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
- Replace=true
- PruneLast=true
- ApplyOutOfSyncOnly=truePer-Resource#
metadata:
annotations:
argocd.argoproj.io/sync-options: ServerSideApply=true,Replace=trueKey options:
- ServerSideApply – Uses Kubernetes server-side apply instead of
kubectl apply. Solves field ownership conflicts and is required for resources with large metadata that exceed the annotation size limit of client-side apply. - Replace – Uses
kubectl replaceinstead ofkubectl apply. Necessary for immutable fields that cannot be patched (like Job selectors). Destructive – use with care. - CreateNamespace – Creates the target namespace if it does not exist.
- PruneLast – Delays deletion of removed resources until after all other sync waves complete.
- ApplyOutOfSyncOnly – Only applies resources that are actually out of sync, skipping those already matching the desired state. Reduces API server load for large applications.
Selective Sync#
Sync only specific resources instead of the entire application:
argocd app sync my-app --resource apps:Deployment:my-app
argocd app sync my-app --resource :Service:my-app
argocd app sync my-app --resource argoproj.io:Rollout:my-appThe format is group:kind:name. Use an empty group for core API resources. This is useful when one resource is failing and you want to sync everything else.
Dealing with Sync Failures and Degraded State#
When a sync fails, ArgoCD marks the application as Degraded or SyncFailed. Common recovery steps:
# See what failed
argocd app get my-app
# Check resource-level status
argocd app resources my-app
# Retry the sync
argocd app sync my-app --retry-limit 3 --retry-backoff-duration 10s
# Force sync (re-applies even if in sync)
argocd app sync my-app --force
# Hard refresh (re-read from Git, clear cache)
argocd app get my-app --hard-refreshIf a PreSync hook Job fails, the entire sync stops. Fix the issue, delete the failed Job if hook-delete-policy is not set to BeforeHookCreation, and retry the sync.