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

URL: https://www.devopsness.com/blog/argo-cd-app-outofsync
Author: Kiril Urbonas
Published: 2026-10-30
Updated: 2026-10-30
Categories: DevOps
Tags: Kubernetes, CI/CD, GitOps, ArgoCD, Troubleshooting

Summary: 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.

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

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
$ 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:

```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
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](/blog/gitops-with-argo-cd-best-practices-for-2025).

## 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

- [Argo CD documentation: Diffing customization](https://argo-cd.readthedocs.io/en/stable/user-guide/diffing/)
- [Argo CD documentation: Sync options](https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/)

