I work across a handful of projects in a day. Each one has its own database URL, API keys, and sometimes its own Node version. Sourcing .env files by hand is tedious, and exporting secrets into my global shell is a bad idea.
direnv loads a per-directory shell environment when I cd in, and unloads it when I leave. That's the whole pitch.
direnv hooks into your shell. Every time you change directory, it walks up the tree looking for an .envrc file. If it finds one, and you've explicitly allowed it, direnv runs the file and injects the resulting environment into your current shell.
Leave the directory, and those variables are gone. No manual unset. No stale DATABASE_URL pointing at the wrong project.
On macOS with Homebrew:
brew install direnv
Then hook it into your shell. For zsh, add this to the bottom of ~/.zshrc:
eval "$(direnv hook zsh)"
For bash, use ~/.bashrc and direnv hook bash. Restart your shell.
Drop an .envrc into a project:
export DATABASE_URL="postgres://localhost:5432/blog"
export NODE_ENV="development"
cd into the directory and direnv will refuse to run it until you approve:
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
Run direnv allow. From now on, every time you enter that directory, those variables are exported. Leave, and they're unset.
The approval is the safety layer. If someone else edits the .envrc, whether a teammate, a dependency, or a malicious patch, direnv blocks it again until you re-approve.
Most of my projects already have a .env file that other tools read. I don't want to duplicate values. .envrc can source them:
dotenv
That's it. direnv reads .env and exports everything. If the file doesn't exist, direnv complains. Use dotenv_if_exists for optional loading.
You can also point at a different file:
dotenv .env.local
Add .envrc to your global gitignore, not the repo's:
.envrc
.direnv/
The repo stays clean for teammates who don't use direnv. Your per-machine setup stays local.
If the .envrc itself is safe to share (no secrets, just dotenv and tooling setup), commit it. Put the actual secrets in .env.local and gitignore that instead.
direnv isn't just for environment variables. The .envrc is a shell script, so anything you'd put in .bashrc works:
bin/ to PATH. PATH_add bin puts ./bin in front, scoped to this directory.use node 22.11.0 via the node layout.layout python3 creates and activates one per project.I mostly use it for environment variables and the occasional PATH_add. The rest is there when I need it.
source .env muscle memory.direnv allow gate means you approve every change before it runs.If you already juggle a few projects with different env files, direnv pays for itself in a week.