How to Set Up direnv for Per Project Environment Variables
You switch from one project to another and the API still points at the wrong database, because an environment variable from the last terminal session stuck around. Or you keep a sticky note of export commands to paste every morning.

You switch from one project to another and the API still points at the wrong database, because an environment variable from the last terminal session stuck around. Or you keep a sticky note of export commands to paste every morning. direnv fixes both problems by loading a project's environment variables when you enter its directory and unloading them the moment you leave.
Setup takes under ten minutes. This guide covers installation, the shell hook, a sensible .envrc layout, how to keep secrets out of Git, and the places where direnv is the wrong tool.
What direnv does and why it helps
direnv is a small shell extension. Every time your prompt is drawn, it checks the current directory and its parents for a file called .envrc. If it finds one that you have approved, it runs it in a subshell, captures the resulting environment changes and applies them to your shell. When you cd out of the project, it reverts those changes.
The result is that each repository carries its own settings. Your shell profile stops collecting project specific exports, and two projects that both use a variable named DATABASE_URL no longer fight. It is one of the cheapest ways to reduce the slow drift between a local setup and production, because the environment a project expects is written down in the project itself.
Installing direnv and hooking it into your shell
Install from your package manager:
- macOS: brew install direnv
- Debian or Ubuntu: sudo apt install direnv
- Fedora: sudo dnf install direnv
- Arch: sudo pacman -S direnv
Then add the hook to the end of your shell config file. The hook is what makes direnv run on each prompt.
- Bash, in ~/.bashrc: eval "$(direnv hook bash)"
- Zsh, in ~/.zshrc: eval "$(direnv hook zsh)"
- Fish, in ~/.config/fish/config.fish: direnv hook fish | source
Open a new terminal and run direnv version. If it prints a version number, around 2.35 at the time of writing, the hook is active.
Your first .envrc file
In the root of a project, create a file named .envrc with a few lines like these:
- export APP_ENV=development
- export DB_HOST=localhost
- export DB_PORT=5432
- export LOG_LEVEL=debug
Save it, and direnv will print a warning that the file is blocked. That is a safety feature. Because .envrc is a shell script, a malicious repository could otherwise run code the moment you cd into it. Run direnv allow to approve the file. From then on, entering the directory loads the variables and leaving unloads them. If anyone edits the file, including you, direnv blocks it again until you re-approve.
Useful built-in helpers
direnv ships a standard library of functions you can call inside .envrc:
- dotenv loads a .env file, so tools that expect that format keep working.
- dotenv_if_exists .env.local loads personal overrides only when the file is present.
- PATH_add bin prepends the project's bin folder to your PATH, handy for project scripts.
- layout python3 creates and activates a Python virtual environment in .direnv automatically.
- use nix or use flake loads a Nix shell if your team uses Nix.
- source_up also loads the .envrc from a parent directory, useful in monorepos.
Keeping secrets out of Git
The common pattern that works well for small teams is a split between shared defaults and personal secrets:
- Commit .envrc with non-secret defaults and a line that reads dotenv_if_exists .env.local.
- Add .env.local and .direnv/ to .gitignore.
- Commit a .env.example listing every variable a developer needs, with dummy values.
- Each developer copies .env.example to .env.local and fills in real keys.
Pair this with a check that blocks secrets from being committed. Our guide to pre-commit hooks small teams keep using covers a secret scanner that takes about two minutes to add.
For credentials you would rather not keep on disk at all.envrc can call a password manager CLI. For example, the 1Password CLI command op read, wrapped in an export line, pulls a value from your vault each time you enter the directory. It adds a second or so to each cd, which is a fair trade for a production key.
Where the usual advice goes too far
Some guides suggest putting everything into .envrc: tool versions, aliases, background services, even commands that start Docker containers. I would not. .envrc runs on every prompt in that directory tree. Anything slow makes every cd feel sluggish, and anything with side effects, like starting a container, will fire more often than you expect, including when you open a new tab or editor terminal.
Keep .envrc to environment variables and PATH changes. Put tasks that do things in a task runner. If you already have a Makefile task runner, a make dev target is the right home for starting services. A good rule: if a line in .envrc takes more than 200 milliseconds or changes anything outside your shell, move it.
direnv is also not a deployment tool. Production environments should get their configuration from the platform, a secrets manager or your orchestration layer, not from a file that relies on a developer's shell hook.
Editor and CI integration
Editors launched from the dock do not inherit your shell's direnv state. VS Code users can install a direnv extension that reads .envrc for the integrated terminal and debugger. JetBrains IDEs have a similar plugin, and Neovim has direnv.vim. In CI, you can run direnv exec . followed by your test command so the job sees the same variables as a developer, though most teams simply set CI variables in the pipeline config instead.
Troubleshooting checklist
- Nothing loads: the hook line is missing or placed before something that resets the prompt. Move it to the very end of the config file.
- Blocked every time: a tool keeps rewriting .envrc. Check for formatters touching it.
- Variable still set after leaving: it was exported in your shell profile too. Remove the duplicate there.
- Slow prompt: run direnv status and time each line of .envrc to find the slow call.
Frequently asked questions
Is direnv safe to use?
Yes, because it refuses to run any .envrc until you approve it with direnv allow, and it blocks the file again whenever the content changes. The main risk is approving a file from a repository you have not read. Open the .envrc and read it before you allow it, the same as any script.
What is the difference between direnv and a .env file?
A .env file is a static list of variables that your application or a library loads at runtime. direnv loads variables into your shell when you enter a directory, so every command you run there sees them. They work well together, since direnv can load a .env file with its dotenv helper.
Does direnv work on Windows?
It works in WSL, Git Bash and MSYS2 with the bash hook, and there is a native Windows binary on the project's releases page. Native PowerShell support is limited, so most Windows developers use direnv inside WSL where it behaves exactly as on Linux.
More in Dev Tools
Dev Tools
Local HTTPS Development With mkcert in Under Ten Minutes
Your app works on plain http://localhost:3000, then breaks the moment it touches a secure cookie, a service worker or an OAuth...
Dev Tools
Makefile Task Runner Setup for Small Projects
Open the README of almost any small project and you will find a block of seven or eight shell commands under a heading like...
Dev Tools
Git Pre-Commit Hooks That Small Teams Actually Keep Using
A teammate pushes a commit with a stray console.log , a 40 MB video file and an AWS key in a config file.