Skip to main content
Argo CD App Always OutOfSync? Find the Field, Then Fix It

Argo CD App Always OutOfSync? Find the Field, Then Fix It

KU
• 6 min read•1 view

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.

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

bash.bash
$ 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 fieldWho changes itBest fix
spec.replicas on a DeploymentHorizontalPodAutoscalerRemove replicas from Git
Injected containers, volumes, annotationsMutating webhooks (service meshes, secret injectors)ignoreDifferences with jq or managed fields
caBundle on webhook configurationscert-manager or the webhook's own controllerSystem-level ignoreDifferences
spec.metrics order on an HPAThe HPA controller reorders itMatch the controller's order in Git
Random values in Secrets or labelsHelm functions like randAlphaNumGenerate once, store externally
Defaulted fieldsAPI server defaultingServer-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:

yaml.yaml
# 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:

yaml.yaml
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 replicas from Git.
  • Sidecar or annotation injected by a webhook? Narrow ignoreDifferences with a jq expression, plus RespectIgnoreDifferences=true.
  • Same noise in every app? Move the rule to resource.customizations in argocd-cm.
  • Defaulted fields and ownership conflicts? Trial ServerSideApply=true on 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

Explore topics:KubernetesCI/CDDevOps
React
Share this post
KU

Kiril Urbonas

AI Engineer

570 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.