For an accurate preview, run kubectl apply --dry-run=server to validate the change through the full admission chain, and kubectl diff -f <file> with --server-side to see exactly what would change. Reserve --dry-run=client for quick local syntax checks, since it skips the API server logic that decides whether a real apply would succeed.
TL;DR:
- Server side dry runs send manifests through API server admission, defaulting, webhook, and custom resource validation, then discard results without changing cluster state.
- Client side dry runs catch local syntax issues but skip admission and defaulting, so use them for quick checks, not production validation.
- kubectl diff returns exit code 0 for no differences, 1 when changes exist, and values above 1 for errors; CI should distinguish them.
- CI identities need PATCH, CREATE, and UPDATE permissions for kubectl diff, and Helm or Kustomize output must be rendered before comparison.
- Server side apply can flag field ownership conflicts; inspect managed fields first, and force ownership only when the competing value should be replaced.
Table of Contents
- What commands and flags preview a Kubernetes change?
- How does kubectl diff build its preview, and how do you read it?
- Client versus server dry-run: which one can you trust?
- How do you resolve field ownership conflicts in a dry run?
- How do you automate previews in CI/CD?
- Which checklist should you run before applying?
- Where do desktop tools fit in a preview workflow?
- Run your previews across clusters without the context switching
- FAQ
- Sources
What commands and flags preview a Kubernetes change?
The --dry-run flag on kubectl apply accepts three values: none, client and server. When you leave it out, the behavior is none, which means the change is really applied. That default is the reason a forgotten flag hurts.
Commands worth keeping in your shell history:
kubectl apply -f pod.yaml --dry-run=serversends the request to the API server and persists nothing.kubectl apply -f pod.yaml --dry-run=clientvalidates and prints the object locally.kubectl diff -f pod.yamlprints a YAML diff between your manifest and the live object.kubectl diff -f pod.yaml --server-sideruns the comparison with server-side semantics.kubectl apply -f pod.yaml --server-side --dry-run=server --force-conflictspreviews a forced ownership takeover.
You can also swap the diff viewer. By default kubectl diff calls a local diff tool with the -u and -N flags, and the KUBECTL_EXTERNAL_DIFF environment variable lets you point it at something more readable, such as colordiff.
Exit status 0, 1 or greater than 1 is how kubectl diff reports no difference, differences found, or an error. That contract is what makes it scriptable.
How does kubectl diff build its preview, and how do you read it?
kubectl diff takes your local manifest, compares it against the object currently in the cluster, and prints the result as YAML-style diff hunks. The diff uses - and + lines to show differences between the live object and your manifest.
Read the exit code before the text:
- Exit 0: no differences, the live object already matches your manifest.
- Exit 1: differences were found. This is a normal result, not a failure, so a naive
set -escript will abort on it. - Exit above 1: kubectl or the diff program itself failed, so the output cannot be trusted.
With --server-side, the comparison uses the API server for a more accurate preview. Expect some noise on the first run: fields you never wrote appear as additions because the server fills them in. That can reveal differences between the manifest and the server-side preview.
Client versus server dry-run: which one can you trust?
Client dry-run validates locally and prints the object that would be sent. It never reaches the API server's logic, so a manifest can pass cleanly and still be rejected on a real apply. Server dry-run submits the request and runs the full admission chain, including defaulting and validation, then discards the result.
- Client mode skips admission controllers, defaulting webhooks and CRD validation.
- Server mode exercises all of them, so it is far closer to a real apply.
- Production-facing changes, and any resource touched by webhooks or custom resources, belong under server mode.
- Client mode earns its keep for a fast syntax check before you even have cluster access.
The same blog post argues that client-side dry-run is not enough for production validation, because the logic that rejects or rewrites your object lives in the API server.
Pro Tip: Make --dry-run=server the muscle-memory default and treat --dry-run=client as a lint step, never as proof that a change is safe.

How do you resolve field ownership conflicts in a dry run?
Server-side apply tracks which manager owns each field. When your manifest touches a field another manager owns, the preview fails with a conflict. That failure is useful, because you learn about it before production does. The server-side apply documentation describes the ways out.
- Re-run with
--force-conflictsto take ownership, only when you are sure the other manager's value should lose. - Align your manifest with the value currently on the server, so both managers agree and the conflict disappears.
- Release management of the field by setting it to null in your configuration.
- Inspect
metadata.managedFieldswithkubectl get -o yaml --show-managed-fieldsto see who owns what before choosing.
Migrating from client-side apply is a related case. Running kubectl apply --server-side with --dry-run=server first shows how field management would transfer, and the last-applied-configuration annotation stays up to date while the kubectl field manager is in use.
Pro Tip: Give each automation its own field-manager name with --field-manager. Conflicts then name the culprit instead of a generic "kubectl".
How do you automate previews in CI/CD?
Run kubectl diff or kubectl apply --dry-run=server as a gating step in every pull request, and map the exit codes deliberately.
- Treat exit 1 from diff as "changes present": post the diff as a PR comment and fail only on codes above 1.
- Grant the CI identity real permissions:
kubectl diffneeds PATCH, CREATE and UPDATE even though nothing is stored, so a read-only role will fail. - Render templates before diffing: for Helm or Kustomize output, pipe the rendered YAML into
kubectl diff -f -. - Use Argo CD rendering for GitOps repos: argocd-diff-preview renders manifests with Argo CD itself, optionally on an ephemeral cluster, so previews never touch production.
This also answers the manual-versus-GitOps question: with plain manifests, you control the diff directly, while with Argo CD the controller does the rendering and you preview through its tooling.
Which checklist should you run before applying?
- Prefer server dry-run for production resources and anything behind admission webhooks.
- Gate merges on diff or dry-run exit codes, or on a structured report built from them.
- Declare server-defaulted fields in your manifests, since fields defaulted on the server but missing locally can cause surprises on later changes.
- Expect limits: a preview client that only records writes does not replace server-side validation, because the critical logic runs in the API server.
A diff that exits 0 means nothing would change; review other results before applying.
Where do desktop tools fit in a preview workflow?
The CLI stays the authority for scripted checks, because exit codes and flags are what pipelines consume. Where a desktop client helps is the interactive side: one place to hold access to many clusters, edit YAML, and jump from a failed preview into logs or a pod shell. When a diff looks wrong on one cluster, switching context quickly matters more than any new flag. We think of GUI tooling as the troubleshooting layer on top of kubectl, never a replacement for it. If you want to go deeper, our guides on verifying rollouts and pod shells show that interactive side, and our notes on multi cluster workflows cover coordination.
— Margus
Run your previews across clusters without the context switching
We built Kubezilla Desktop for engineers who run many diffs and dry-runs a day: it keeps your kubeconfigs stored locally, connects to multiple clusters at once, and puts live logs, YAML editing, Helm and Argo CD operations in one native app. Your kubectl habits stay intact, with less terminal juggling around them. It costs 5 EUR per month, and you can get Kubezilla Desktop today.

FAQ
What does kubectl apply --dry-run=server actually do?
It sends your manifest to the API server, which runs the full admission chain and validation, then discards the result without persisting anything. The kubectl apply reference lists server as one of three accepted values alongside client and none.
Is kubectl diff safe to run against production?
It does not persist changes, but it does call the API server, so the identity running it needs PATCH, CREATE and UPDATE permissions. Choose an identity with the permissions needed to run the preview.
Why does my dry run pass but the real apply fail?
You most likely used client-side dry-run, which skips admission controllers, defaulting and CRD validation. Re-run with --dry-run=server, and add --server-side if field ownership conflicts could be involved.
What does exit code 1 from kubectl diff mean?
It means differences were found, not that the command broke. Only exit codes above 1 indicate an error, so CI scripts should treat 0 and 1 as successful runs and branch on the difference.
