So far every sync has simply applied our manifests and waited for them to become healthy. But real deployments often need extra steps at specific moments — running a database migration before the new version rolls out, or a smoke test after. ArgoCD gives us Resource Hooks to run these tasks at the right point in a sync.
Sync phases
When ArgoCD syncs an Application, it moves through a series of phases. A hook is just a
normal Kubernetes resource (usually a Job or Pod) annotated to run in one of them:
- PreSync — runs before the main manifests are applied. Perfect for schema migrations or seeding data.
- Sync — runs together with the main wave of resources.
- PostSync — runs after all resources are applied and report healthy. Great for smoke tests, cache warming, or notifications.
- SyncFail — runs only when the sync fails. Use it for cleanup or alerting.
We select a phase with the argocd.argoproj.io/hook annotation.
A PreSync migration hook
Here’s a classic example: run a database migration as a Job before the application
is updated.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: myorg/book-info-migrations:1.4.0
command: ["/bin/sh", "-c", "migrate up"]
backoffLimit: 1
Because it’s a PreSync hook, ArgoCD creates this Job and waits for it to complete
successfully before applying the rest of the manifests. If the migration fails, the
sync stops and your new version is never rolled out against an out-of-date schema.
Cleaning up hooks: delete policies
Hook resources would pile up if we never removed them, so ArgoCD provides the
argocd.argoproj.io/hook-delete-policy annotation:
- HookSucceeded — delete the hook once it completes successfully.
- HookFailed — delete the hook if it fails (keep the logs of successful ones).
- BeforeHookCreation — delete the previous hook instance right before creating a new one on the next sync. This is the most common choice for Jobs, because Job specs are immutable and re-applying a Job with the same name would otherwise error.
A robust migration hook usually combines two:
metadata:
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
A PostSync smoke test
After the new version is live, run a quick health check:
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:
restartPolicy: Never
containers:
- name: curl
image: curlimages/curl:8.9.1
command:
- sh
- -c
- "curl -sf http://productpage:9080/health || exit 1"
backoffLimit: 0
If the smoke test Job fails, ArgoCD marks the sync operation as failed, which you can wire up to notifications or a rollback.
Ordering resources with sync waves
Hooks control when a task runs relative to the phases. To order the regular
resources within a phase, use sync waves with the
argocd.argoproj.io/sync-wave annotation. Lower numbers are applied first; ArgoCD
waits for each wave to become healthy before starting the next.
# Applied first
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
---
# Applied after wave 0 is healthy
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1"
A typical layout:
- wave
-1: aPreSyncmigration hook - wave
0: theDeploymentandService - wave
1: anIngressthat should only appear once the app is up
Sync waves also apply to hooks, so you can, for example, run several PreSync hooks in a deterministic order.
Health and the sync order, together
Putting it all together, a sync of the Book Info app might look like this:
- PreSync — run
db-migrate; wait for success. - Sync — apply the Deployment (wave 0), then the Ingress (wave 1), waiting for each wave to be healthy.
- PostSync — run
smoke-test; if it fails, the operation is marked failed.
You can find the manifests in 07-resource-hooks.
Resource hooks turn ArgoCD from a “kubectl apply on a loop” into a proper deployment pipeline that understands the order your application needs.
In the next post, we’ll tackle one of the trickiest parts of GitOps: Secret Management with GitOps — how to keep secrets in Git without leaking them.