Open your .env file. Count the entries. If it's over 20, something is off. If it's over 40, you have a smell.
The smell isn't that you have secrets. The smell is that the .env file has stopped being a list of secrets and started being a dump of everything that's even slightly sensitive or environment-specific. There's a real distinction between those, and collapsing it has real consequences.
The two categories that get conflated
An .env file should hold one thing: secrets. Things that, if leaked, give an attacker access to something they shouldn't have. API keys. Database passwords. Webhook signing secrets. Stuff with consequences.
Free tier — 250 calls/month, no card required.
What ends up in .env files in practice: secrets plus config. Things like:
API_BASE_URL
OPENAI_MODEL
FEATURE_FLAG_NEW_HEADER
DEBUG_MODE
None of those are secrets. They're configuration. They might vary between environments, but if they leaked, nothing bad happens.
Why mixing them is a problem
Three reasons.
One: rotation discipline goes to zero. If you have to update 40 entries every time you "rotate the .env file," you stop rotating. The work is too painful, and the file mostly contains config anyway, so the secret-rotation feels like overkill.
Two: sharing becomes dangerous. When a teammate asks "can you send me the staging .env so I can run the app locally," you have to choose between sending them everything (including real prod secrets that snuck into staging) or carefully filtering. The filter step gets skipped.
Three: the audit trail is missing. You can't tell from the .env file when each secret was last rotated, who has access, what scopes it has. It's just a flat text file. That's fine for a list of config — terrible for a list of credentials.
The split
Move secrets into a vault (IBYOK or any equivalent). The app fetches them at startup or on-demand. Move config into a config file (config.json, settings.toml, whatever your stack uses). The config file can be checked into git — it doesn't contain anything sensitive.
The .env file shrinks to almost nothing. Maybe one entry: VAULT_TOKEN=... — the credential that lets the app fetch its other credentials from the vault.
That single entry is the only thing that needs to live in .env. Everything else either lives in the vault (secrets) or in the config file (config). Different homes, different lifecycles, different security postures.
The setup pain (it's overstated)
"Sounds like more setup." It's about an hour for an existing project. You move secrets into the vault one by one, add a small fetch helper to your code, replace the .env reads with vault reads.
For a new project, it's the same amount of work as setting up the .env file in the first place. There's no real cost penalty after the first project — and the cost benefits show up immediately on every subsequent rotation, every shared credential, every "wait, is this secret in our git history" panic.
The .env file isn't evil. It's just been asked to do too many jobs. Hand the secrets to a vault and let .env shrink to one line. Your future self will appreciate it.
— Jeff
Free tier — 250 calls/month, no card required.