Diff Two Config Files — Why git diff Wasn't Enough

webdev diff devops config tools

env.staging and env.prod “look the same” until a side-by-side diff paints a red trailing space on STRIPE_SECRET_KEY. Git was never involved — both files came from a password manager export. That is when a focused text diff beats git diff.

Situations git does not see

ScenarioWhy git is awkward
Two vault exportsNot in the working tree
Customer XML vs your sampleOutside the repo
Prod box copy vs localDownloaded artifacts
Email’d CSV vs sheet exportAd hoc files

Paste both sides into a text diff tool before you overwrite anything in production.

Boring bugs diffs catch

Trailing whitespace on secrets — looks identical until whitespace mode is on.
CRLF vs LF — bash on Linux chokes on ^M.
Key order reshuffles — sort both files first, or use structured JSON diff.
Duplicate .env keys — last assignment wins; a unified diff against known-good surfaces the extra line.
BOM — UTF-8 with BOM vs without creates a phantom line-1 diff that breaks parsers expecting { or KEY=.

Workflow that prevents outages

  1. Freeze copies (prod.env.bak)
  2. Normalize only what you intend (UTF-8, final newline)
  3. Diff with whitespace visible
  4. Decide source of truth
  5. Apply deliberately — no merge-by-eyeball
diff -u staging.env prod.env
git diff --no-index -- staging.env prod.env
jq -S . a.json > a.sorted.json
jq -S . b.json > b.sorted.json
diff -u a.sorted.json b.sorted.json

--no-index is the git escape hatch for files outside commits.

Format tips

FormatTip
JSONPretty-print with sorted keys before diff
YAMLShow invisibles; tabs vs spaces matter
Nginx / K8sComment and include order matter

Binary files (ZIP, PNG, compiled plists) need hashes (sha256sum), not text diff — mangling “config” that is actually binary creates corruption.

Human process

Redact secrets before pasting into shared UIs. Prefer local/browser-side diff for credentials. Attach the hunk in the change ticket.

Release checklist: staging vs prod compared; no trailing spaces on tokens; line endings OK for target OS; feature flags match; rollback copy stored. Git history tells you what changed in the repo. A two-file diff tells you what is about to hit the server. Use both.

Permission and ownership diffs

Sometimes the bytes match and production still fails because mode, owner, or SELinux labels differ. After a text diff comes back clean, verify deployment metadata (ls -l, container config mounts). A “perfect” env file with the wrong Unix permissions is still an outage. Keep text diff as step one, not the only step, when debugging “identical configs.”

Ordered vs semantic equality

For JSON, key order differences are often noise; for nginx configs, directive order can change behavior. Know which class of file you are diffing before you “normalize everything.” Sorting JSON is safe; sorting YAML Kubernetes manifests may reorder list items that are position-sensitive (containers, volumes). Prefer tool-aware normalize steps.

Keep a short runbook link in your incident channel: “diff staging vs prod configs before restart.” The habit prevents more outages than any fancy observability dashboard when the root cause is a single trailing space.

If your team rotates on-call, practice the staging-vs-prod diff once in a drill so nobody discovers the whitespace view toggle during a real Sev-1.

Semantic vs textual diffs

Whitespace-only changes can drown real edits. Normalize line endings before comparing configs. For code, prefer git diff with ignore-space options when reviewing noisy formatting PRs.

Binary files

Diff tools that treat ZIP or images as text produce garbage. Detect binary early and compare checksums or use specialized image diffs instead.