Keychain vs .env files for developers (complete comparison)
Both solve “get a secret into process.env eventually.” They differ in how secrets live between runs: OS-managed encrypted storage vs plaintext files next to your code.
1. Quick comparison
| Dimension | .env file | Keychain (direct or via app) |
|---|---|---|
| At-rest in project tree | Plaintext | Not in repo folder |
| Accidental zip / sync | High risk | Lower |
| Ergonomics | Trivial cat | Needs app or security CLI |
| CI servers | Common | Rare (use CI secrets) |
| Cross-platform | Yes | macOS / Apple ecosystem |
| Team sync | Dangerous | Per-device or wrapped export |
Apple docs: Keychain Services
2. Threat model translation
- Casual file theft (copy folder to USB): Keychain wins.
- Malware on unlocked Mac: Neither is a strong boundary — assume compromise; reduce scope and lifetime.
- Git leak: Keychain wins indirectly (secret not in tree) — still need Git hygiene.
3. Why not raw Keychain for everything?
Ad-hoc security add-generic-password scripts scale poorly:
- Hard to audit what you created six months ago.
- Easy to log secrets accidentally.
- Access groups are easy to get wrong.
Prefer a developer vault that uses Keychain under the hood: PassStore · Security.
4. Hybrid pattern (recommended)
- Store canonical values in PassStore / Keychain-backed vault.
- Inject into environment for runtime:
export STRIPE_SECRET_KEY="$(security find-generic-password -s STRIPE_SECRET_KEY -w)"
# Prefer GUI vault copy over scripting for routine work
npm run dev
- Gitignore any generated
.envif your tooling requires a file.
5. When .env is still reasonable
- Purely local test keys with low blast radius.
- Docker Compose
env_fileworkflows with short-lived files. - Teaching demos — never real keys.
Read: is .env safe?