Git, CI/CD & GitOpsLXXVII · Argo CDSync
Sync policies and sync windows — automated, manual, prune, and the time-bound gate
What you'll learn
- Distinguish automated sync from manual sync and identify the operational trade-off of each
- Configure prune and self-heal independently and recognise the failure mode each prevents
- Define sync windows that gate automated syncs to a maintenance window
- Choose the right sync policy for production vs non-production environments
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The Application’s syncPolicy is the gate between “controller
detected a diff” and “controller applied the diff”. Without a
sync policy, every Application is manual: the controller surfaces
the diff and an operator runs argocd app sync to apply it.
With an automated sync policy, the controller applies the diff
itself, with sub-options for pruning and self-healing that
control how it applies. Sync windows gate automated syncs to
specific time bands.
Automated versus manual
The two top-level sync modes:
- Manual. The controller reconciles, detects the diff,
updates
status.sync.statustoOutOfSync, and waits. The operator reviews and runsargocd app sync NAMEto apply. Manual sync is the default and the right choice for high-risk production Applications. - Automated. The controller reconciles, detects the diff, and
applies it. The
syncPolicy.automatedblock carries sub-options that shape what “applies” means. The right choice for Applications where the diff is well-understood and the cost of an out-of-cycle apply is lower than the cost of waiting.
argocd app set payment-api \
--sync-policy automated
flowchart LR
D["Diff detected"] --> P{"syncPolicy"}
P -->|manual| W["Wait for argocd app sync"]
P -->|automated| A["Apply diff"]
W --> O["Operator review"]
O --> S["argocd app sync"]
S --> A
A --> K["Cluster updated"]
The trade-off is control versus latency. Manual sync adds a human in the loop; automated sync removes the human but also removes the latency. A team that runs hundreds of Applications across many clusters cannot review every diff manually; automated sync is the operational answer.
Prune and self-heal
Two sub-options inside syncPolicy.automated control what the
controller does when desired and live diverge:
- Prune. When the rendered source no longer contains a resource that exists in the cluster, the controller deletes it. Pruning is destructive: a typo that removes a resource from the chart deletes it from the cluster. Right for Applications whose resource set is owned end-to-end by the chart.
- Self-heal. When the live state differs from the desired
state for reasons the controller did not cause (a manual
kubectl edit, an out-of-band Helm install), the controller reapplies desired state. Right for Applications where the chart is the single source of truth and any drift is a bug.
The two are independent. A team can run automated sync with prune but without self-heal (prunes deleted resources but does not revert out-of-band edits), or with self-heal but without prune (reverts drift but does not delete resources that disappeared from the source).
syncPolicy:
automated:
prune: true
selfHeal: true
argocd app set payment-api \
--sync-policy automated \
--auto-prune \
--self-heal
Sync windows
A sync window is a time band during which automated sync is allowed (or denied). Sync windows are defined at the AppProject level, not per-Application, because the maintenance window is a property of the environment, not of the workload.
- Allow. During the window, automated sync is permitted. Outside the window, automated sync is blocked; manual sync still works.
- Deny. During the window, automated sync is blocked. Outside the window, automated sync is permitted.
kind: AppProject
metadata:
name: payments
spec:
syncWindows:
- kind: allow
schedule: '0 2 * * *'
duration: 4h
applications:
- 'payment-*'
manualSync: true
The fields and what they own:
kind—allowordeny.schedule— a cron expression. The window starts at each cron tick.duration— how long the window stays open. A four-hour allow window at0 2 * * *covers 02:00-06:00 UTC daily.applications— a glob list of Application names. The window applies only to the listed Applications.manualSync— when true, manual sync is also permitted during a deny window. Useful for emergencies.
gantt
title Sync window - allow 02:00-06:00 UTC daily
dateFormat HH:mm
axisFormat %H:%M
section Day 1
Window (allow) :active, 02:00, 4h
Blocked : 06:00, 22h
section Day 2
Window (allow) :active, 02:00, 4h
A production team that runs scheduled releases uses sync windows to gate automated syncs to a maintenance window. The window is the operational contract: production changes happen at 02:00 UTC and not at any other time unless a human invokes manual sync.
Sync options
The syncOptions field is an array of strings that toggle
per-sync behaviour. Options most production teams need:
CreateNamespace=true— creates the destination namespace if it does not exist.PrunePropagationPolicy=foreground— controls prune order. Foreground blocks dependents until the owner is deleted.PruneLast=true— applies new resources before deleting pruned ones. Reduces the partial-applied window.ApplyOutOfSyncOnly=true— applies only out-of-sync resources. Faster for large sets with small diffs.ServerSideApply=true— uses server-side apply. The right choice for resources with shared ownership and managedFields.
Under the hood
The controller’s reconcile loop is a state machine with three
states: Synced, OutOfSync, Unknown. The transition between
OutOfSync and Synced is the apply; the transition between
Synced and OutOfSync is the detection. The sync policy decides
what happens during the transition out of OutOfSync: with manual
sync the controller waits; with automated it applies. The sync
window is a gate on the automated transition.
Production discipline
The rules for production sync policies:
- Manual sync is the default for production Applications that
change state. A manual policy forces a human to invoke
argocd app sync, which forces a review of the diff, which forces the change to be intentional. prune: trueis enabled only after a namespace backup. Pruning deletes resources. The first time a chart prunes a resource, the team must be able to restore it.selfHeal: trueis enabled when the chart owns the live state. Self-healing reverts out-of-band edits. A team with legitimate reasons to edit live state (debugging, one-off certificates) must disable self-heal for those resources.- Sync windows are defined per environment, not per workload. The window is the operational contract; the workload inherits it from the project.
Cross-course references
- Kubernetes for Production Sysadmins - Part XVI (Operators and Controllers) covers the reconcile loop pattern that Argo CD’s sync policy implements.
- Git, CI/CD & GitOps for Infrastructure Engineers - Part LXXV (Pull-based Deployment) is the operational pattern; Part LXXVI-05 (Promotion Models) is how the sync window fits into a promotion pipeline.
Quiz
Knowledge check · 4 questions
Q1. A production Application uses automated sync with prune enabled. An engineer renames a resource in the chart and commits. What does the controller do?
Q2. A sync window of kind: deny blocks manual sync as well as automated sync by default.
Q3. Name the two sub-options of syncPolicy.automated and identify which one reverts out-of-band kubectl edits.
Q4. Diagnose why the production deploy happened outside the maintenance window and recommend a fix.
A team has automated sync enabled with a sync window of kind: allow, schedule '0 2 * * *', duration 4h. The controller is observed applying a production change at 14:30 UTC on a Tuesday. The Application's status shows the change was applied as automated sync.
Passing score: 75%. Answers are checked in this browser.