Diff Two Config Files — Why git diff Wasn't Enough
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
| Scenario | Why git is awkward |
|---|---|
| Two vault exports | Not in the working tree |
| Customer XML vs your sample | Outside the repo |
| Prod box copy vs local | Downloaded artifacts |
| Email’d CSV vs sheet export | Ad 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
- Freeze copies (
prod.env.bak) - Normalize only what you intend (UTF-8, final newline)
- Diff with whitespace visible
- Decide source of truth
- 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
| Format | Tip |
|---|---|
| JSON | Pretty-print with sorted keys before diff |
| YAML | Show invisibles; tabs vs spaces matter |
| Nginx / K8s | Comment 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.