direnv Environment Manager

direnv cheatsheet — auto-load env vars per directory with .envrc. direnv allow, direnv deny, direnv reload. Use layout python, layout node. No more manual exports.

6 min read

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 ~/.bashrc or ~/.bash_profile.
  • Zsh: Add eval "$(direnv hook zsh)" to ~/.zshrc.
  • Fish: Add direnv hook fish | source to ~/.config/fish/config.fish.
  • Nushell: direnv hook nu | save ~/.config/envman.nu and add source ~/.config/envman.nu to ~/.config/nushell/config.nu.

Then, restart your shell or source your configuration file (e.g., source ~/.bashrc).

Core Concepts

  • .envrc file: This is the primary configuration file for direnv. It’s a shell script that direnv executes 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 .envrc file, direnv will not load it for security reasons. You must explicitly direnv allow or direnv deny for that directory. Once allowed, it will automatically load/unload on subsequent entries/exits.
  • Virtual Environments: direnv is commonly used to activate virtual environments (like Python’s venv or conda) or load specific SDKs (like Node.js versions via nvm).

Commands / Usage

Managing .envrc Files

  • direnv edit

    • Purpose: Opens the .envrc file for the current directory in your default editor. If the file doesn’t exist, it will be created.
    • Example:
      direnv edit .
      
      This command opens ~/<your_project_dir>/.envrc for editing.
  • direnv allow

    • Purpose: Explicitly allows the .envrc file in the current directory to be loaded. You’ll be prompted for this the first time you cd into a directory with an .envrc.
    • Example:
      direnv allow
      
  • direnv deny

    • Purpose: Explicitly denies the .envrc file in the current directory from being loaded. This overrides any previous allow status.
    • Example:
      direnv deny
      
  • direnv status

    • Purpose: Shows the current status of direnv for the current directory, including whether the .envrc is allowed and what environment variables are loaded.
    • Example:
      direnv status
      

Loading and Unloading Environment Variables

  • direnv (no arguments)

    • Purpose: Manually loads the .envrc file for the current directory if it’s not already loaded or if changes have been detected. This is usually done automatically by direnv when you cd.
    • Example:
      direnv
      
  • direnv unload

    • Purpose: Unloads the environment variables that were loaded by direnv for the current directory. This is usually done automatically when you cd out of a directory.
    • Example:
      direnv unload
      

Helper Commands

  • direnv exec <command>

    • Purpose: Executes a command within the environment loaded by direnv for 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
      
  • 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
      
  • direnv help

    • Purpose: Displays help information for direnv commands.
    • Example:
      direnv help
      

Flags (less commonly used directly, more for customization)

  • DIRENV_LOG (Environment Variable)

    • Purpose: Controls direnv’s logging level. Useful for debugging.
    • Example (in .bashrc or similar):
      export DIRENV_LOG=3 # Set to DEBUG level
      eval "$(direnv hook bash)"
      
  • DIRENV_DIR (Environment Variable)

    • Purpose: Specifies a directory to watch for direnv events, overriding the default behavior of watching the current directory and its ancestors.
    • Example:
      export DIRENV_DIR=/path/to/watch
      eval "$(direnv hook bash)"
      

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: direnv will never automatically load an .envrc file without explicit direnv allow. This prevents malicious scripts from injecting unwanted environment variables into your shell. Always review .envrc files before allowing them.
  • Shell restart: After installing direnv and adding the hook to your shell configuration, you must restart your shell or source the configuration file for the hooks to take effect.
  • cd is magic: direnv works by intercepting cd commands. If direnv isn’t hooked into your shell, it won’t work.
  • Ancestor directories: direnv checks for .envrc files in the current directory and all ancestor directories. This means variables from higher-level .envrc files can be overridden by variables in lower-level ones.
  • PATH manipulation: When modifying PATH, be careful not to overwrite it entirely. Use PATH_add or prepend/append correctly:
    # .envrc
    PATH_add /usr/local/bin
    # or
    export PATH="/opt/mytool/bin:$PATH"
    
  • Order of execution: Variables defined later in the .envrc file (or in lower directories) take precedence over those defined earlier.
  • No export needed (usually): For variables defined directly in .envrc, direnv automatically exports them. You typically only need export if you’re defining a variable within a shell function or complex logic inside the .envrc. Using PATH_add is preferred over manual export PATH=....