Argo CD App Always OutOfSync? Find the Field, Then Fix It
The sync succeeds, the app goes green for a second, then OutOfSync again. Almost always a controller is changing a field Git also sets. How to find it and the three fixes, in order of preference.
Key takeaways
- An Argo CD app that returns to OutOfSync after every sync usually has a field that something else changes after apply, such as an HPA setting replicas, a webhook injecting sidecars, or defaults filled by the API server.
- Run argocd app diff to find it, then remove the field from Git, add ignoreDifferences with RespectIgnoreDifferences, or use server-side apply.
On this page
When an Argo CD Application syncs and then flips back to OutOfSync, Argo CD isn't broken. It's correctly reporting that the live object doesn't match Git, because something changes the object after Argo CD applies it.
The Argo CD docs list the usual suspects: a controller or mutating webhook altering the object after it was submitted, fields Kubernetes drops or defaults, and templates that render differently every time.
The fix is always the same shape: find the exact field, then decide who should own it.
Step 1: find the field
$ argocd app get shop --hard-refresh # rule out a stale cached manifest first
$ argocd app diff shop
===== apps/Deployment shop/web ======
14c14
< replicas: 7
---
> replicas: 3
The diff names the resource and the field: here the live object runs 7 replicas while Git says 3. In the UI, the same view is the "Diff" tab on the out-of-sync resource. If argocd app diff shows nothing but the status stays OutOfSync, look for resources that need pruning: an object removed from Git but still live, with auto-prune disabled, keeps the app out of sync.
The usual causes
| Drifting field | Who changes it | Best fix |
|---|---|---|
spec.replicas on a Deployment | HorizontalPodAutoscaler | Remove replicas from Git |
| Injected containers, volumes, annotations | Mutating webhooks (service meshes, secret injectors) | ignoreDifferences with jq or managed fields |
caBundle on webhook configurations | cert-manager or the webhook's own controller | System-level ignoreDifferences |
spec.metrics order on an HPA | The HPA controller reorders it | Match the controller's order in Git |
| Random values in Secrets or labels | Helm functions like randAlphaNum | Generate once, store externally |
| Defaulted fields | API server defaulting | Server-side apply, or set them explicitly |
Fix 1: stop declaring fields you don't own
The cleanest fix is to remove the field from Git when something else is supposed to manage it. If an HPA controls a Deployment, delete replicas from the Deployment manifest; the HPA sets it and Argo CD has nothing to diff. In Helm charts, that usually means wrapping replicas in a condition that skips it when autoscaling is enabled.
When the chart is the problem
Helm charts cause a special class of permanent drift. Argo CD renders charts with helm template, so template functions that depend on the live cluster behave differently than under helm install. Functions like randAlphaNum generate a new value on every render, and lookup returns nothing during rendering, which defeats the usual "reuse the existing secret" pattern.
The fix is to move generated values out of the chart: create the secret once and reference it, or manage it with External Secrets or Sealed Secrets. Checksum annotations computed from rendered config are fine, as long as their inputs are stable between renders.
Fix 2: tell Argo CD to ignore it, during diff and sync
When you can't remove the field, ignore it explicitly. By default, ignoreDifferences only affects the diff that decides sync status; during sync, Argo CD still applies the desired state as-is, which can fight the controller. Adding RespectIgnoreDifferences=true makes the sync respect the same rules:
# apps/shop.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop
namespace: argocd
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
- group: apps
kind: Deployment
jqPathExpressions:
- .spec.template.spec.containers[] | select(.name == "istio-proxy")
syncPolicy:
syncOptions:
- RespectIgnoreDifferences=true
For fields a known controller manages, managedFieldsManagers ignores everything that manager owns, such as kube-controller-manager. For cluster-wide noise, like caBundle on every webhook configuration, put the rule in resource.customizations in the argocd-cm ConfigMap instead of repeating it in every Application.
Keep ignore rules narrow. A broad rule hides real drift, which defeats the reason for running GitOps.
Fix 3: server-side apply for defaulting and ownership
Some diffs come from the gap between what you sent and what the API server stored, such as defaulted fields or fields owned by other managers. Enabling server-side apply lets Kubernetes track field ownership:
spec:
syncPolicy:
syncOptions:
- ServerSideApply=true
Test it on one Application first; it changes how conflicts surface. For broader Argo CD practices, see our GitOps with Argo CD best practices.
Questions people ask
Why does my Argo CD application keep going OutOfSync?
Something changes a field after Argo CD applies it. Common culprits are an HPA setting spec.replicas, a mutating webhook injecting sidecars or annotations, controllers rewriting fields such as caBundle, and Helm templates generating random values. Run argocd app diff <app> to see the exact field, then remove it from Git or ignore it.
How do I make Argo CD ignore replicas when using an HPA?
The best option is removing replicas from the Deployment in Git, so the HPA owns it alone. If you can't, add an ignoreDifferences entry with the JSON pointer /spec/replicas for Deployments, plus the RespectIgnoreDifferences=true sync option so syncs don't reset the replica count.
What does RespectIgnoreDifferences do in Argo CD?
By default, ignoreDifferences only affects whether an app shows as synced; the sync itself still applies the full desired state, which can overwrite fields a controller manages. With RespectIgnoreDifferences=true, Argo CD also skips those ignored fields during sync.
The decision, concretely
- Replicas drifting with an HPA? Remove
replicasfrom Git. - Sidecar or annotation injected by a webhook? Narrow
ignoreDifferenceswith a jq expression, plusRespectIgnoreDifferences=true. - Same noise in every app? Move the rule to
resource.customizationsinargocd-cm. - Defaulted fields and ownership conflicts? Trial
ServerSideApply=trueon one Application.
The call we'd make
Find the field before changing any setting, because the right fix depends entirely on who owns it. Prefer removing fields from Git over ignoring them, and keep every ignore rule as narrow as a single path. An app that is honestly in sync is worth more than a green badge hiding drift.
Sources
More from DevOps
Explore more articles in this category
Kubernetes HPA Not Scaling? The 7 Causes, in Order
The HorizontalPodAutoscaler is up, traffic is spiking, and replicas won't move. Seven causes in the order we check them, each with the command that confirms it.
GitHub Actions Security: 7 Fixes After the Trivy Compromise
In March 2026 an attacker force-pushed 76 trivy-action tags to an infostealer that read secrets straight out of runner memory. Seven fixes that would have contained it, in the order we'd apply them.
Do You Need Kubernetes in 2026? A Decision Framework
Kubernetes is the right answer for fewer teams than run it. Five questions that tell you whether you need it, and what to run instead when you don't.
You might have missed
Evergreen posts worth revisiting.