kubectl diff: Usage, Options & Common CI Errors
See exactly what an apply will change - before it changes it.
kubectl diff compares your manifests against the live objects and prints the differences, using the same server-side merge apply would. It is the safety check to run before a deploy and a useful PR artifact.
What it does
kubectl diff -f sends your manifest to the API server in dry-run mode, gets back the would-be result, and diffs it against the current state. It exits 0 when there is no diff, 1 when there is a diff, and >1 on error - handy for gating. It reflects defaulting and admission mutations, so it shows the real outcome, not just a text diff.
Common usage
kubectl diff -f manifests/
kubectl diff -k overlays/prod
kubectl diff -f deploy.yaml || echo "changes pending"
KUBECTL_EXTERNAL_DIFF="colordiff" kubectl diff -f deploy.yamlCommon errors in CI
The exit-code semantics trip up scripts: a pipeline that runs set -e treats diff's exit 1 (there is a diff) as a failure and aborts. Either tolerate exit 1 explicitly (kubectl diff -f x || true) or branch on it. "forbidden: User cannot patch resource" during diff means the service account lacks the dry-run/patch permission the diff needs. Diff requires the same RBAC as apply. And like apply, server-side diff needs a reachable API server; an unreachable cluster gives a connection error, not an empty diff.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes