Ordering Rollouts in Argo CD with Sync Waves and Selective Sync
Use Argo CD sync waves to order resource rollout inside one Application and selective sync to recover a single failed resource without re-applying everything.
19 Jan 2026, 02:04 UTC

The problem: Argo CD applies everything at once
By default, an Argo CD sync applies all resources in an Application without a meaningful order guarantee. If your Deployment needs a ConfigMap to exist first, or a Job must run only after the new Pods are healthy, a flat sync can produce transient failures, crash loops, or a half-applied release. Sync waves give you explicit ordering inside one Application, and selective sync gives you a scalpel for recovering a single broken resource without re-applying everything.
The takeaway: annotate resources with argocd.argoproj.io/sync-wave integers (lower waves apply first, and Argo CD waits for each wave to report Healthy before starting the next), and use argocd app sync --resource when you need to fix one resource in isolation.
Prerequisites
- A running Argo CD instance (behavior described here applies to the 2.x line; verify against your version's docs).
- An Application pointing at a Git repository containing your manifests.
- Health assessment working for your resource kinds. Argo CD ships built-in health checks for common kinds (Deployments, StatefulSets, Services, Ingress, Jobs). Custom resources need a custom health check in
argocd-cm, or Argo CD will treat them as healthy immediately and waves will not actually gate on them. - The
argocdCLI authenticated, or UI access with sync permissions.
How sync waves actually work
Each resource can carry the annotation argocd.argoproj.io/sync-wave with an integer value (default is 0). During a sync, Argo CD:
- Sorts resources by wave number, lowest first. Negative values are allowed and run before wave 0.
- Applies all resources in the current wave.
- Waits until every resource in that wave reports Healthy before starting the next wave.
- Halts the entire sync if a wave fails to become healthy, leaving later waves unapplied.
Two limits matter. First, waves only order operations within one sync of one Application — they cannot wait for a database managed by a different Application. Second, hooks (PreSync, Sync, PostSync) also honor wave annotations, which can produce surprising ordering if you mix hooks and waves. Keep hook waves clearly separated from workload waves.
Procedure: annotate a staged rollout
A common layering looks like this:
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: shop
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: shop-config
namespace: shop
annotations:
argocd.argoproj.io/sync-wave: "0"
data:
FEATURE_FLAG: "on"
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
namespace: shop
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
replicas: 2
selector:
matchLabels:
app: shop-api
template:
metadata:
labels:
app: shop-api
spec:
containers:
- name: api
image: example/shop-api:1.4.0
readinessProbe:
httpGet:
path: /healthz
port: 8080
---
# migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: shop-migrate
namespace: shop
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: example/shop-api:1.4.0
command: ["./migrate"]Notes on the details:
- Quote the annotation values. Kubernetes annotations are strings;
sync-wave: "2"is safe, while an unquoted value can be parsed as an integer by some tooling and rejected on apply. - The readiness probe is the gate. Wave 2 (the Job) will not start until the Deployment in wave 1 is Healthy, and a Deployment is Healthy only when its Pods pass readiness. Without a probe, the gate is weak.
- Commit and push to the tracked Git branch, then sync from your machine:
argocd app sync shop(requires sync permission on theshopApplication). If auto-sync is enabled, the sync triggers on its own.
Selective sync for targeted recovery
When one resource in a wave is stuck — say the migration Job failed and you have fixed its manifest — you do not need to re-sync the whole Application. Sync just that resource:
argocd app sync shop --resource batch:Job:shop-migrateThe --resource flag takes group:kind:name (use the core group as empty, e.g., :ConfigMap:shop-config). You can repeat the flag for multiple resources. Add --prune if the resource was removed from Git and you want it deleted rather than applied. In the UI, the same operation is available by selecting resources on the Application details page and choosing Sync.
Verification checks
- Run
argocd app get shop --refreshand confirm Sync Status isSyncedand Health isHealthy. - Confirm ordering: in the UI, resources show their wave numbers; you can also watch
kubectl get events -n shop --watchduring a fresh sync and observe the namespace, ConfigMap, Deployment, then Job appearing in sequence. - Test the failure path deliberately: temporarily point the Deployment's readiness probe at a bad path, push, and sync. The expected result is that waves -1 and 0 apply, wave 1 never becomes Healthy, wave 2 never starts, and the Application sits in
OutOfSync/Degraded. If the Job still runs, your health gating is not working — check that the Deployment has a real readiness probe. - If you use auto-sync with self-heal, disable self-heal temporarily for this test; otherwise retries can mask the failure behavior you are trying to observe.
Recovery options
- Fix forward: correct the manifest in Git and re-sync. This is the preferred path in a GitOps workflow.
- Revert:
git revertthe bad commit and let auto-sync (or a manual sync) restore the prior state.argocd app rollbackexists but requires the previous manifests to still be available from the Application's history and is generally discouraged in favor of Git reverts. - Remove a stuck resource: delete it from Git and run
argocd app sync shop --resource batch:Job:shop-migrate --pruneto prune just that object. - Unblock a halted wave: if a wave is blocked on a resource that will never become healthy (for example, a completed-but-failed Job), deleting that resource in the cluster or via selective prune lets the next sync proceed.
Limitations to keep in mind
Sync waves do not replace dependency management across Applications — for that, look at App-of-Apps patterns or ApplicationSets with their own ordering mechanisms. Waves also add latency: each wave waits for full health, so a slow-starting Deployment delays everything after it. Finally, verify the annotation behavior against your Argo CD version before relying on it in production; the mechanics described here are long-standing, but hook/wave interaction details have changed across releases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.