codel
← Back to the index

OPS-06 · SEC. 03 Operations & DevOps

Env Var Drift Audit: Code vs Template vs Platform

Reconcile every env var across code, .env.example, and your deploy platform until nothing is unaccounted for.

FORMAT
goal
DIFFICULTY
advanced
TIME
25 min
TOOLS
claude-code · codex-cli
MODELS
any
COPIES
0 so far

When to use this

A deploy just failed on a missing env var, or a new machine couldn't boot the project, and you suspect the real inventory of variables lives in three places that quietly disagree: the code, the .env.example, and the deploy platform's dashboard.

The pattern

Pastes as plain text
Goal: zero unaccounted-for environment variables in this project. A
variable is accounted for when it's consistent across code, the env
template, and the deploy platform, or has a written reason it isn't.

1. Build the inventory from code, which is the source of truth: search
   the codebase for every variable actually read (process.env, os.environ,
   import.meta.env, or this stack's equivalent).
2. Compare against .env.example (or whatever env template this repo uses).
3. Compare against the deploy platform, per environment: list what's set
   using the platform CLI if one is available (e.g. `vercel env ls`).
   If you can't read it from here, print the exact command for me to run
   and work from the output I paste back.
4. Produce a table: variable | read in code | in template | in platform
   (per env) | verdict. Verdicts:
   - OK: present everywhere it needs to be.
   - MISSING: read in code, absent from the platform. Breaks deploys.
   - ORPHANED: set somewhere, never read in code. Delete candidate.
   - UNDOCUMENTED: read in code and set in platform, but absent from the
     template, so a fresh clone can't boot.
5. Fix what needs no secrets: add every UNDOCUMENTED name to the template
   with a placeholder value and a one-line comment. Never write a real
   secret value into any tracked file.
6. For MISSING and ORPHANED rows, output the exact platform command to
   fix each one, but do not run them. Changing live config is my call.

Done means the table has zero MISSING and zero UNDOCUMENTED rows, and
every ORPHANED row has either a removal command or a reason to stay.

Real example output

Inventory: 14 variables read in code, 11 in .env.example, 12 set in Vercel.

| Variable              | Code | Template | Prod | Preview | Verdict |
|-----------------------|------|----------|------|---------|---------|
| DATABASE_URL          | yes  | yes      | yes  | yes     | OK |
| STRIPE_WEBHOOK_SECRET | yes  | yes      | yes  | no      | MISSING (preview): webhook tests 500 on preview deploys |
| RESEND_API_KEY        | yes  | no       | yes  | yes     | UNDOCUMENTED -> added to .env.example |
| ADMIN_ALLOWLIST       | yes  | no       | yes  | yes     | UNDOCUMENTED -> added to .env.example |
| NEXT_PUBLIC_GA_ID     | no   | yes      | yes  | yes     | ORPHANED: analytics removed in March, safe to delete |
| ...9 more OK rows...  |      |          |      |         |    |

Template updated: 2 names added with placeholders, no real values written.
Commands for you to run:
  vercel env add STRIPE_WEBHOOK_SECRET preview
  vercel env rm NEXT_PUBLIC_GA_ID production
  vercel env rm NEXT_PUBLIC_GA_ID preview

Remaining: 0 MISSING after you run line 1, 0 UNDOCUMENTED, 1 ORPHANED
with removal commands above.

Why it works

Making the code the source of truth ends the argument about which list is right: a variable nobody reads is dead weight no matter who set it, and a variable the code reads must exist everywhere the code runs. The four verdicts map one-to-one onto real failure modes, so the table is a work order, not a report.

Entry OPS-06 · by codel · 2026-07-09 · CC-BY-4.0