TL;DR
- dotenv — loads
.envinto your app's process at runtime. Lives inside the app. - direnv — loads
.envrcinto your shell when youcdinto the directory. Lives outside the app. - They solve different problems. Many teams use both.
What dotenv actually does
dotenv is a library you import in your app code. On startup, it reads a .env file and merges the values into process.env. That's it. The shell you launched the app from doesn't see these variables at all.
// server.js
import 'dotenv/config';
console.log(process.env.DATABASE_URL); // works
// terminal
$ echo $DATABASE_URL
# empty — dotenv only loaded it into the Node processWhat direnv actually does
direnv is a shell extension. When you cd into a directory with an .envrc, it automatically exports those variables into your current shell. When you cd out, they unload.
$ cd ~/projects/api
direnv: loading ~/projects/api/.envrc
direnv: export +DATABASE_URL +JWT_SECRET
$ echo $DATABASE_URL
postgres://localhost/api
$ cd ..
direnv: unloadingNow every CLI tool you run — psql, redis-cli, pytest, ad-hoc scripts — sees the right values automatically. No source .env, no wrapping commands in node --env-file=.env.
When to use which
| Scenario | Pick |
|---|---|
Your app needs process.env.KEY at runtime | dotenv (or Node 20+ --env-file) |
You want psql / redis-cli to "just work" in the project dir | direnv |
| Python venv + env vars activated together | direnv (via layout python) |
| CI/CD pipelines | Neither — use host env injection |
| Production | Neither — host dashboard or secrets manager |
Using them together
The common production-grade setup on macOS/Linux dev machines:
# .envrc (loaded into shell by direnv)
dotenv # loads .env into shell via direnv's dotenv helper
source .venv/bin/activate # if Python
export PATH="./node_modules/.bin:$PATH"
# .env (consumed by your app at runtime via dotenv lib)
DATABASE_URL=postgres://localhost/app
JWT_SECRET=...Now npm run dev works (dotenv lib reads .env) and psql in your terminal hits the right DB (direnv exported DATABASE_URL to the shell).
Security considerations
Both .env and .envrc sit in your repo directory and should be git-ignored (how-to).
direnv has one safety feature dotenv doesn't: allow-listing. When you first enter a directory with an .envrc, direnv refuses to load it until you explicitly run direnv allow. This prevents a malicious git clone from exporting attacker-controlled env vars into your shell.
Switching between the two
Going from dotenv to direnv: keep .env, add a .envrc that just contains dotenv. Done.
Going from direnv to dotenv: move anything that isn't KEY=value (shell functions, PATH manipulation, venv activation) out of .envrc and into a wrapper script. What's left goes into .env.
Recommendation
For solo projects: start with dotenv. For any team larger than one, or if you regularly run CLI tools against your dev DB, add direnv on top. The combination is the best-in-class local-dev env setup in 2026.
For production, neither of these is the answer — see our dotenv vs secrets manager comparison.