The problem
So far you pressed Sync yourself (26.5, 26.6). That still leaves a human in the loop for every change, and drift is only reported, never fixed. You want Argo CD to apply each commit by itself, delete what was removed from git, and undo changes made behind its back - and you want control over the order things are applied in (a database migration before the new app version).
What you need to know already: 26.4 (Application, sync, sync and health status), 26.6 (drift and self-heal seen once), 25.26 (Helm hooks: a Job that runs before or after an upgrade), 15.24 (Jobs), 15.26 (annotations).
Three switches
syncPolicy:
automated:
prune: true # delete live objects that are no longer in git
selfHeal: true # re-sync when the LIVE state drifts, not only when git changes
allowEmpty: false # refuse to sync if git renders nothing (protects against a bad path)
- Manual (no
automatedblock): Argo CD only reports OutOfSync; a human or a pipeline runsargocd app sync. Useful for prod in teams that want an explicit button, or while moving a team to GitOps. - automated (auto-sync): every new commit that renders differently is synced. Without
selfHeal, akubectl editon a live object stays until the next commit. - prune: without it, deleting a file from git leaves the object running and the app OutOfSync (the object is marked "requires pruning"). Pruning = deleting from the cluster what is no longer in git. Many teams enable auto-sync but not auto-prune for prod, and prune in a reviewed manual sync.
- selfHeal: when the live state drifts, Argo CD syncs again (after a short wait). This is what makes git the only way in - and what reverts hotfixes done with kubectl, which is the next incident.
Auto-sync does not retry a commit that already failed: if the sync of commit a1b2c3 fails, it stays failed until a new commit (or a manual sync). Add retry: if short-lived failures (a component not ready yet, a CRD still installing) should be retried:
retry:
limit: 5 # at most 5 more tries
backoff: { duration: 5s, factor: 2, maxDuration: 3m } # wait 5s, 10s, 20s... up to 3m
The same switches from the shell: argocd app set APP --sync-policy automated --auto-prune --self-heal.
Sync options
Sync options tune how a sync applies things. They go in syncPolicy.syncOptions:
CreateNamespace=true create destination.namespace if missing
PruneLast=true prune after everything else is applied and healthy
ApplyOutOfSyncOnly=true only apply what differs (big apps: faster, less load on the API)
ServerSideApply=true use kubectl apply --server-side (the API server tracks which
tool owns which field; avoids the 262 KiB size limit of the
last-applied annotation - big CRDs, big ConfigMaps)
Replace=true kubectl replace/create instead of apply (last resort)
RespectIgnoreDifferences=true do not overwrite fields listed in ignoreDifferences on sync (26.12)
Validate=false skip schema validation
Per object, as annotations:
argocd.argoproj.io/sync-options: Prune=false- never prune this (a PVC with data, a namespace).argocd.argoproj.io/sync-options: Delete=false- keep it when the app is deleted.argocd.argoproj.io/compare-options: IgnoreExtraneous- do not count it for sync status (objects generated by something else).
Phases and hooks
A sync runs in phases, in order: PreSync -> Sync -> PostSync, and SyncFail only if it failed. Your normal manifests are in the Sync phase.
A sync hook is any manifest - usually a Job - annotated to run in another phase:
metadata:
annotations:
argocd.argoproj.io/hook: PreSync # PreSync | Sync | PostSync | SyncFail | Skip
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation # | HookSucceeded | HookFailed
The delete policy says when Argo CD deletes the old hook Job: BeforeHookCreation = just before creating it again on the next sync (so you can still read the last run's logs); HookSucceeded / HookFailed = right after it succeeds / fails.
A failed PreSync Job stops the sync before any Deployment changes - the database migration pattern from 25.26. When Argo CD renders a Helm chart, it maps Helm's pre-install/pre-upgrade hooks to PreSync and post-* to PostSync. helm.sh/hook: test is ignored.
Waves
Within a phase, sync waves order the apply:
argocd.argoproj.io/sync-wave: "-1" # lower first; default 0; must be a STRING (quoted)
Argo CD applies wave -1, waits until those objects are healthy, then wave 0, and so on. Typical use: CRDs and Namespaces at -2, ConfigMaps/Secrets at -1, the app at 0, a smoke-test Job (a quick "does it answer?" check) as a PostSync hook. Inside one wave, objects go in kind order (Namespaces, then ConfigMaps/Secrets, then Services, then Deployments...).
Deleting an app
A finalizer is a marker on a Kubernetes object that says "someone must clean up before this object may disappear" (the API waits until it is removed). An Application with the finalizer resources-finalizer.argocd.argoproj.io deletes everything it deployed when you delete it (cascade). Without it, deleting the Application leaves the objects running, orphaned. argocd app delete cascades by default; kubectl delete application only if the finalizer is set. Know which one your team uses before you "just recreate the app".
What you can now do
- Choose between manual and automated sync, with or without prune and self-heal.
- Run a Job before the rollout with a PreSync hook, and order objects with waves.
- Predict what deleting an Application does to what it deployed.