What it is
direnv is a tool that loads and unloads environment variables automatically as you cd into and out of directories, making it easy to manage project-specific configurations without cluttering your shell.
Installation
Linux
# Using apt (Debian/Ubuntu)
sudo apt update
sudo apt install direnv
# Using dnf (Fedora)
sudo dnf install direnv
# Using pacman (Arch Linux)
sudo pacman -S direnv
# Using nix
nix-env -iA nixpkgs.direnv
macOS
# Using Homebrew
brew install direnv
Windows
(Requires a Unix-like environment such as Git Bash, Cygwin, or Windows Subsystem for Linux)
# Using Chocolatey (in an administrator PowerShell/CMD)
choco install direnv
Shell Integration
After installing direnv, you must hook it into your shell. Add the appropriate line to your shell’s configuration file:
- Bash: Add
eval "$(direnv hook bash)"to~/.bashrcor~/.bash_profile. - Zsh: Add
eval "$(direnv hook zsh)"to~/.zshrc. - Fish: Add
direnv hook fish | sourceto~/.config/fish/config.fish. - Nushell:
direnv hook nu | save ~/.config/envman.nuand addsource ~/.config/envman.nuto~/.config/nushell/config.nu.
Then, restart your shell or source your configuration file (e.g., source ~/.bashrc).
Core Concepts
.envrcfile: This is the primary configuration file fordirenv. It’s a shell script thatdirenvexecutes when you enter a directory containing it. This script defines the environment variables for that directory.- Allowing/Denying: The first time you enter a directory with an
.envrcfile,direnvwill not load it for security reasons. You must explicitlydirenv allowordirenv denyfor that directory. Once allowed, it will automatically load/unload on subsequent entries/exits. - Virtual Environments:
direnvis commonly used to activate virtual environments (like Python’svenvorconda) or load specific SDKs (like Node.js versions vianvm).
Commands / Usage
Managing .envrc Files
-
direnv edit- Purpose: Opens the
.envrcfile for the current directory in your default editor. If the file doesn’t exist, it will be created. - Example:
This command opensdirenv edit .~/<your_project_dir>/.envrcfor editing.
- Purpose: Opens the
-
direnv allow- Purpose: Explicitly allows the
.envrcfile in the current directory to be loaded. You’ll be prompted for this the first time youcdinto a directory with an.envrc. - Example:
direnv allow
- Purpose: Explicitly allows the
-
direnv deny- Purpose: Explicitly denies the
.envrcfile in the current directory from being loaded. This overrides any previousallowstatus. - Example:
direnv deny
- Purpose: Explicitly denies the
-
direnv status- Purpose: Shows the current status of
direnvfor the current directory, including whether the.envrcis allowed and what environment variables are loaded. - Example:
direnv status
- Purpose: Shows the current status of
Loading and Unloading Environment Variables
-
direnv(no arguments)- Purpose: Manually loads the
.envrcfile for the current directory if it’s not already loaded or if changes have been detected. This is usually done automatically bydirenvwhen youcd. - Example:
direnv
- Purpose: Manually loads the
-
direnv unload- Purpose: Unloads the environment variables that were loaded by
direnvfor the current directory. This is usually done automatically when youcdout of a directory. - Example:
direnv unload
- Purpose: Unloads the environment variables that were loaded by
Helper Commands
-
direnv exec <command>- Purpose: Executes a command within the environment loaded by
direnvfor the current directory, without permanently loading the environment into your current shell session. Useful for one-off commands. - Example:
direnv exec python manage.py migrate
- Purpose: Executes a command within the environment loaded by
-
direnv mk.bashrc/direnv mk.zsh/direnv mk.fish- Purpose: Generates the shell hook script for your chosen shell. This is typically done automatically during installation or via the
eval "$(direnv hook ...)"command. - Example:
direnv mk.bashrc > ~/.direnv_bashrc echo 'source ~/.direnv_bashrc' >> ~/.bashrc
- Purpose: Generates the shell hook script for your chosen shell. This is typically done automatically during installation or via the
-
direnv help- Purpose: Displays help information for
direnvcommands. - Example:
direnv help
- Purpose: Displays help information for
Flags (less commonly used directly, more for customization)
-
DIRENV_LOG(Environment Variable)- Purpose: Controls
direnv’s logging level. Useful for debugging. - Example (in
.bashrcor similar):export DIRENV_LOG=3 # Set to DEBUG level eval "$(direnv hook bash)"
- Purpose: Controls
-
DIRENV_DIR(Environment Variable)- Purpose: Specifies a directory to watch for
direnvevents, overriding the default behavior of watching the current directory and its ancestors. - Example:
export DIRENV_DIR=/path/to/watch eval "$(direnv hook bash)"
- Purpose: Specifies a directory to watch for
Common Patterns
Python Virtual Environments
Create a .envrc file in your project root:
# .envrc
layout python
Then run direnv allow. direnv will automatically create and activate a virtual environment named .direnv/python-X.Y.Z (where X.Y.Z is your Python version) upon entering the directory.
Node.js Version Management (with nvm)
Add these lines to your .envrc:
# .envrc
source ~/.nvm/nvm.sh
nvm use 18
Then run direnv allow. direnv will source nvm and switch to Node.js version 18.
Setting Specific Variables
Define variables directly in .envrc:
# .envrc
export API_KEY="abcdef12345"
export DATABASE_URL="postgres://user:pass@host:port/db"
Then run direnv allow.
Using .env files
If you have a .env file (common in many frameworks):
# .envrc
dotenv
Then run direnv allow. direnv will load variables from .env into the environment.
Loading based on directory structure (using direnv_layout)
direnv provides helper functions for common scenarios.
# .envrc
direnv_layout node # For Node.js projects
This is often equivalent to manually sourcing nvm and setting the Node version.
Conditional Loading
You can use standard shell scripting within .envrc:
# .envrc
if [[ -f ".secrets.env" ]]; then
dotenv ".secrets.env"
fi
Locking Environment Variables
To ensure consistency, you can "lock" the environment. After setting up your desired variables, run:
direnv status -l > .envrc.local
This creates a .envrc.local file. Future direnv loads will use this file, ensuring the exact same environment variables are loaded. You can then direnv allow and commit both .envrc and .envrc.local.
Ignoring Directories
To prevent direnv from recursing into certain directories (e.g., node_modules), you can use the DIRENV_EXCLUDE_PATTERN environment variable. Add this to your shell’s startup file (.bashrc, .zshrc):
export DIRENV_EXCLUDE_PATTERN="node_modules"
Gotchas
- Security:
direnvwill never automatically load an.envrcfile without explicitdirenv allow. This prevents malicious scripts from injecting unwanted environment variables into your shell. Always review.envrcfiles before allowing them. - Shell restart: After installing
direnvand adding the hook to your shell configuration, you must restart your shell orsourcethe configuration file for the hooks to take effect. cdis magic:direnvworks by interceptingcdcommands. Ifdirenvisn’t hooked into your shell, it won’t work.- Ancestor directories:
direnvchecks for.envrcfiles in the current directory and all ancestor directories. This means variables from higher-level.envrcfiles can be overridden by variables in lower-level ones. PATHmanipulation: When modifyingPATH, be careful not to overwrite it entirely. UsePATH_addor prepend/append correctly:# .envrc PATH_add /usr/local/bin # or export PATH="/opt/mytool/bin:$PATH"- Order of execution: Variables defined later in the
.envrcfile (or in lower directories) take precedence over those defined earlier. - No
exportneeded (usually): For variables defined directly in.envrc,direnvautomatically exports them. You typically only needexportif you’re defining a variable within a shell function or complex logic inside the.envrc. UsingPATH_addis preferred over manualexport PATH=....