← Back to blog

argocd app diff: Preview Changes Before You Sync

November 6, 2026
argocd app diff: Preview Changes Before You Sync

argocd app diff compares the desired state of an Argo CD application with what is running in the cluster and prints the difference. According to the official command reference, it performs a diff against the target and live state and renders it with diff. Run argocd app diff my-app before a manual sync and you see what the sync would change. That includes resources a prune would remove.

This guide covers the command, the flags that matter, how to read the output, the exit codes that trip up CI jobs, and how to check pruning before it bites.

Kubezilla Desktop
Review Argo CD Changes in One Place
Kubezilla Desktop brings ArgoCD operations and live cluster management into one native app, alongside secure local kubeconfig storage.
Visit Kubezilla Desktop

TL;DR

  • argocd app diff APPNAME shows the gap between the target state (Git) and the live cluster. No output means the app is in sync.
  • Exit code 0 means no diff, 1 means a diff was found, and 2 means an error. Append || true if a pipeline should not fail on a diff.
  • Server-side diff predicts the live state more accurately, because it uses a dry-run Server-Side Apply. It can fail if RBAC is missing.
  • Automated sync does not prune by default. Use Prune=confirm, Prune=false or PruneLast=true to control deletions.
  • Kubernetes Secrets are ignored in this diff, so check them another way.

Table of Contents

What argocd app diff compares

Argo CD tracks two states for every application. The target state is what your Git repository (rendered through Helm, Kustomize or plain manifests) says should exist. The live state is what the cluster actually holds. The diff command puts those side by side.

The output goes through the standard diff tool. If you prefer another viewer, the command reference says the KUBECTL_EXTERNAL_DIFF environment variable selects your own diff tool. Engineers who like a side-by-side terminal viewer or a graphical diff set that variable once and keep the same command.

Use the command in three situations:

  1. Before a manual sync. You want to see exactly what will change.
  2. When an app shows OutOfSync and you do not know why. The diff points to the field or resource responsible.
  3. In a pull request or pipeline. The exit code tells a script whether the cluster and Git differ.

Running the command

The basic form takes the application name:

argocd app diff my-app

A few variations cover most of what you need.

GoalCommand or flag
Diff a live app against its tracked targetargocd app diff my-app
Diff against a specific commitargocd app diff my-app --revision <commit-sha>
Diff local files against the live clusterargocd app diff my-app --local ./path
Set the repo root for local diffs--local-repo-root
Use server-side diff--server-side-diff

The --revision flag expects a commit SHA, so a branch-style typo or a bad access setup can produce an error that looks like an Argo CD fault but is not one.

The --local flag is the one developers reach for. As one practitioner write-up puts it, it compares your local files against the live cluster, so you can preview a change without committing. Pair it with --local-repo-root when your manifests live in a subdirectory of a larger repo.

If you manage apps with multiple sources, check the version you run. The CLI had a gap with multi-source applications, tracked in a long-running GitHub issue, so confirm behavior on your release before you build a pipeline around it.

How to read the output

The output is a unified diff. Each changed resource starts with a header line that names the API group, kind, namespace and name, for example ===== apps/Deployment my-app-ns/my-app ======. Below it, lines starting with - are in the live state but not in the desired state, and lines starting with + are in the desired state but not live, as described here.

A small example:

===== apps/Deployment my-app-ns/my-app ======
-  replicas: 2
+  replicas: 4

Read it as: the cluster has two replicas, Git asks for four, and a sync will scale the Deployment up.

Three habits make large diffs manageable:

  • Scan the headers first. They tell you which resources are touched before you read any detail.
  • Filter by kind. The command has no built-in resource filter, but you can pipe the output through grep, for instance argocd app diff my-app 2>&1 | grep -A 20 "Deployment", a trick from the same write-up.
  • Watch the size. A diff that touches dozens of resources deserves a slower review than a one-line change.

An empty result means the application is in sync.

Exit codes and CI pipelines

The command reference documents three exit codes: 0 when no diff is found, 1 when a diff is found, and 2 on a general error. The non-zero exit on a diff is intentional. A GitHub issue explains it follows the Unix diff convention so CI can detect differences.

Argo CD diff exit code meanings

That is useful for a gate and a nuisance for a report. If a job should only print the diff and continue, the workaround suggested in the same issue is:

argocd app diff my-app || true

If you need to treat only real errors as failures, check for exit code 2 explicitly rather than swallowing everything.

One version note: the --diff-exit-code flag arrived in v3.x, according to a reported issue. Anyone on a v2 CLI who passes it gets an unknown flag error. Match your CLI version to the feature you want.

Diff strategies and server-side diff

Why do some diffs show noise, or miss changes the cluster will make anyway? The answer is the diff strategy. The diff strategies documentation lists three:

  • Legacy: a 3-way diff based on live state, desired state and the last-applied-configuration annotation.
  • Structured-Merge Diff: discontinued.
  • Server-Side Diff: invokes a Server-Side Apply in dry-run mode to generate the predicted live state.

Server-side diff is used automatically for applications that enable the Server-Side Apply sync option. You can override it with the argocd.argoproj.io/compare-options: ServerSideDiff=false annotation, which sends the app back to the legacy client-side diff. On the CLI, the --server-side-diff flag turns it on, and per the command reference it defaults to true when the ServerSideDiff annotation is set on the application.

The practical gain is accuracy. One practitioner source notes that server-side diff accounts for defaults the API server would set, admission validation, and mutating webhook changes when IncludeMutationWebhook=true is enabled on the application. That is the same reason we like server-side checks for manifests in general: our post on Kubernetes dry runs shows that only a server dry run exercises admission, and our piece on YAML validation covers what linters miss.

When server-side diff fails

Server-side diff needs permission to perform that dry-run apply. A GitHub issue from February 2026 documents errors when the argocd-server service account lacks RBAC for certain resources such as ClusterRole. The discussed fixes were to extend the API server's ClusterRole or to use impersonation of the controller service account. If you see permission errors only on certain kinds, start there.

Why an app is OutOfSync right after a sync

A clean sync followed by an immediate OutOfSync is a common surprise. The diffing customization docs list the usual causes:

  • Manifests with extra or unknown fields.
  • A sync run with pruning disabled, where resources still need deletion.
  • Controllers or mutating webhooks that alter objects after apply.
  • Helm template functions such as randAlphaNum that generate different data on each render.
  • HPA controllers reordering spec.metrics.

The diff output tells you which of these you have. A field that flips on every render points to the template. A field that a controller rewrites points to an ignore rule.

Ignoring fields on purpose

The same documentation says Argo CD can ignore differences at a JSON path, using RFC6902 JSON patches and JQ path expressions. It can also ignore fields owned by specific managers, as recorded in metadata.managedFields on live resources. For system-wide rules, the release 2.14 docs say you can set ignoreDifferences in the resource.customizations key of the argocd-cm ConfigMap, and apply one to every resource in every application with resource.customizations.ignoreDifferences.all.

To see what is excluded, the troubleshooting tools page documents argocd admin settings resource-overrides ignore-differences, and argocd admin settings validate for a basic settings check.

If you template with Helm, our guide to Helm with Argo CD covers how rendered charts end up as the target state you are diffing.

What the diff will not show: Secrets

The command reference states that Kubernetes Secrets are ignored from this diff. A changed Secret will not appear in the output, and a clean diff is not proof that every Secret matches. Review Secret changes through your secret management workflow, and treat the diff as covering everything else.

Pruning: preview it before it deletes

Pruning is where a diff earns its keep, because deletion is the one change you cannot casually undo. The Automated Sync Policy docs say that, as a safety mechanism, automated sync does not delete resources that are no longer defined in Git by default. To prune, you can run a manual sync with pruning checked.

The mechanics, per the same page:

  • Enable automatic pruning with argocd app set <APPNAME> --auto-prune or spec.syncPolicy.automated.prune: true.
  • Allow an app to sync to an empty set of resources with --allow-empty or spec.syncPolicy.automated.allowEmpty: true. Use this deliberately.

The sync options documentation adds more control:

OptionEffect
Prune=falsePrevents an object from being pruned, set as an annotation on the resource or in the Application's syncOptions.
Prune=confirmRequires manual confirmation before pruning, given in the UI, the CLI or by the argocd.argoproj.io/deletion-approved: true annotation.
PruneLast=trueDefers pruning to a final wave, after other resources are deployed, healthy and all other waves succeeded.
PrunePropagationPolicyChooses background, foreground or orphan deletion. Pruning uses foreground by default.

There is a diff side effect worth knowing. When a resource has Prune=false, the application shows as OutOfSync if Argo CD expects the resource to be pruned, according to the release 2.10 sync options docs. An app that never goes green may simply be holding a resource you protected.

A safe pruning routine

  1. Run argocd app diff my-app and look for resources that exist in the cluster but are gone from Git.
  2. Mark anything critical, such as a PVC or a database, with Prune=false or Prune=confirm.
  3. Sync manually with pruning checked, not through automation, the first time.
  4. Use PruneLast=true when other resources must be healthy before old ones disappear.
  5. Turn on auto-prune only once the diffs have been boring for a while.

Best practices for diff review

One practitioner recommends five habits: run a diff before every manual sync, use local diff during development, enable server-side diff for production apps, watch diff size, and automate diff in the PR workflow so reviewers see it. They match what we see in practice. Add one more: make the diff the last check, not the only one. Review the change in Git first, and treat the diff as the final look at what reaches the cluster.

Our perspective

Most diff trouble we see is not about the command. It is about context. You run argocd app diff, see a wall of text, and then you need the pod logs, the live YAML, the Helm values and the right cluster, usually in four different windows.

We built Kubezilla Desktop around that problem. It is a native app, written in Rust, that puts multi-cluster access, live log streaming, metrics, YAML editing, pod terminals, and Helm and ArgoCD operations in one interface. The idea is to cut down on switching between CLI tools and browser tabs while you work out why an app drifted.

We also keep kubeconfigs in secure local storage, with no cloud sync or telemetry, because the machine you review changes from should not be the weakest part of your delivery chain. Use the CLI for the diff in your pipeline. Use a desktop client for the investigation that follows.

Margus

Check the cluster side in one place

Once a diff tells you what differs, you usually need to look at the live resource, its logs and its events. Kubezilla Desktop connects to multiple clusters, supports SSH tunneling, and gives you ArgoCD and Helm operations next to live logs and metrics. It runs on Linux, macOS and Windows and costs €5 per month.

Kubezilla Desktop

If you work across several clusters, our write-up on Argo CD across clusters shows how that setup works without static tokens. You can also read about the Kubernetes client we ship.

FAQ

What is the difference between argocd app diff and the diff in the Argo CD UI?

Both compare target and live state. The CLI version fits scripts and terminals, and you can swap the viewer with KUBECTL_EXTERNAL_DIFF. The UI is easier for browsing a single app. Choose the CLI when you need an exit code or local files.

Why does argocd app diff return exit code 1?

It means a diff was found. The behavior is intentional and follows the Unix diff convention so CI can spot differences. If your job should continue, use argocd app diff APPNAME || true. Exit code 2 signals a real error.

Can argocd app diff show what pruning would delete?

The diff compares Git with the cluster, so resources that are live but no longer declared are the candidates a prune removes. Because automated sync does not prune by default, review the diff, then use Prune=confirm or Prune=false for resources you cannot afford to lose.

Why do my Secrets not show up in the diff?

The command reference states that Kubernetes Secrets are ignored from this diff. An empty diff does not confirm Secret values match, so verify them through your secrets workflow.

Do I need server-side diff?

It is worth enabling when your cluster uses admission controllers or mutating webhooks, because it predicts the live state through a dry-run apply. Make sure the argocd-server account has the RBAC it needs, or you may hit permission errors on some resource kinds.

Sources