compareupdated 2026-04-19

dotenv vs direnv: which env loader should you use?

dotenv loads .env inside your app at runtime. direnv loads per-directory env into your shell. Comparison of use cases, trade-offs, and when you want both.

TL;DR

  • dotenv — loads .env into your app's process at runtime. Lives inside the app.
  • direnv — loads .envrc into your shell when you cd into 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 process

What 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: unloading

Now 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

ScenarioPick
Your app needs process.env.KEY at runtimedotenv (or Node 20+ --env-file)
You want psql / redis-cli to "just work" in the project dirdirenv
Python venv + env vars activated togetherdirenv (via layout python)
CI/CD pipelinesNeither — use host env injection
ProductionNeither — 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.

Related guides

Related tools