Last updated on 5th August 2026

direnv: Per-Project Environment Variables, Loaded Automatically

You switch from one project to another and forget to update your environment — now you're running commands against the wrong database, or a script fails because a variable from the last project is still set. Manually source-ing an env file every time you change directories is exactly the kind of chore people forget to do.

direnv removes the chore. It loads a project's environment variables the moment you cd into its directory and unloads them the moment you leave — automatically, with no command to remember. It hooks into your shell and reads a file called .envrc from the current directory tree.

How direnv works

direnv sits between you and your shell. When you enter a directory, it looks for an .envrc file, runs it, and applies any variables it exports to your current shell session. When you leave, it reverts those changes. The result: each project gets exactly the environment it expects, and only while you're in it.

Crucially, direnv won't run an .envrc until you explicitly approve it — more on that below — so cloning a repo can't silently execute code in your shell.

Installing direnv

# macOS / Linux
brew install direnv

# Debian / Ubuntu
sudo apt install direnv

Then hook direnv into your shell so it can load and unload automatically:

# ~/.bashrc
eval "$(direnv hook bash)"

# ~/.zshrc
eval "$(direnv hook zsh)"

Your first .envrc

Create an .envrc file in your project root:

export DATABASE_URL="postgres://localhost/myapp_dev"
export NODE_ENV="development"
export API_BASE="http://localhost:4000"

The first time direnv sees it, it refuses to load it and warns you:

direnv: error .envrc is blocked. Run `direnv allow` to approve its content

This is the security model: direnv never runs an .envrc you haven't explicitly trusted. Review the file, then approve it:

direnv allow

From now on, entering the directory loads those variables and leaving unsets them. Edit the file and direnv asks you to allow again, so a change can't sneak in unreviewed.

Loading an existing .env file

If you already keep variables in a .env file, direnv can load it with one line instead of duplicating everything:

# .envrc
dotenv

You can also layer setup on top — add a directory to your PATH, load language-specific tooling, or inherit a parent directory's environment with source_up:

# .envrc
dotenv
PATH_add ./bin
source_up

Keep secrets out of plaintext

.envrc and .env are convenient, but they're plaintext — don't commit real secrets in them. Add .envrc to your .gitignore, or keep only non-sensitive defaults in the committed file. For secrets you do want version-controlled, encrypt your .env files with dotenvx and let direnv load the decrypted values, so nothing sensitive lands in the repository in the clear.

direnv and DeployHQ

direnv is a local development convenience: it shapes your shell environment while you work. Your deployment environment is configured separately, and that separation is intentional — you don't want your laptop's .envrc deciding what production sees.

On the DeployHQ side, set the values your app needs at deploy time as config files or environment values in your project settings, and add any build steps to your build pipeline. Then enable automatic deployments so each push builds and ships with that server-side configuration. The mental model: direnv handles your machine; DeployHQ handles the deploy — and dotenvx-encrypted files are how a secret safely travels between the two.

direnv vs a bundled tool

direnv does one job — per-directory environment — and does it cleanly. If you're also managing language runtime versions and project tasks, you may not want three separate tools: you can handle env, runtimes, and tasks together with mise, which includes the same auto-loading behavior direnv provides. Choose direnv when environment loading is all you need; reach for mise when you want one config file to cover more.

Ready to ship with the right configuration on every deploy? Create a free DeployHQ account and connect your repository in minutes.