Guide for using direnv - a shell extension for loading directory-specific environment variables. Use when setting up project environments, creating .envrc files, configuring per-project environment variables, integrating with Python/Node/Ruby/Go layouts, working with Nix flakes, or troubleshooting environment loading issues on macOS and Linux.
Installs into .claude/skills of the current project.
Are you the author of Direnv?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/bg-szy-direnv)
---
name: direnv
description: Guide for using direnv - a shell extension for loading directory-specific environment variables. Use when setting up project environments, creating .envrc files, configuring per-project environment variables, integrating with Python/Node/Ruby/Go layouts, working with Nix flakes, or troubleshooting environment loading issues on macOS and Linux.
---
# direnv Skill
This skill provides comprehensive guidance for working with direnv, covering installation, configuration, stdlib functions, and best practices for per-project environment management.
## When to Use This Skill
Use this skill when:
- Installing and configuring direnv on macOS or Linux
- Creating or modifying `.envrc` files for projects
- Setting up per-project environment variables
- Configuring language-specific layouts (Python, Node.js, Ruby, Go, Perl)
- Integrating direnv with Nix or Nix Flakes
- Managing secrets and environment configuration for teams
- Troubleshooting environment loading issues
- Creating custom direnv extensions
## Core Concepts
### What is direnv?
direnv is a shell extension that loads and unloads environment variables based on the current directory. When you `cd` into a directory with a `.envrc` file, direnv automatically loads the environment. When you leave, it unloads the changes.
### Security Model
direnv uses an allowlist-based security approach:
- New or modified `.envrc` files must be explicitly allowed with `direnv allow`
- Prevents automatic execution of untrusted scripts
- Use `direnv deny` to revoke access
### How It Works
1. Shell hook intercepts directory changes
2. Checks for `.envrc` file in current or parent directories
3. If allowed, executes `.envrc` in a bash subshell
4. Captures exported variables and applies them to current shell
## Installation
### macOS (Homebrew - Recommended)
```bash
brew install direnv
```
### Linux
```bash
# Ubuntu/Debian
sudo apt install direnv
# Fedora
sudo dnf install direnv
# Arch
sudo pacman -S direnv
# Binary installer (any system)
curl -sfL https://direnv.net/install.sh | bash
```
### Verify Installation
```bash
direnv version
```
## Shell Configuration
Add the hook to your shell's config file. **This is required for direnv to function.**
### Zsh (~/.zshrc)
```bash
eval "$(direnv hook zsh)"
```
**With Oh My Zsh:**
```bash
plugins=(... direnv)
```
### Bash (~/.bashrc)
```bash
eval "$(direnv hook bash)"
```
**Important:** Place after rvm, git-prompt, and other prompt-modifying extensions.
### Fish (~/.config/fish/config.fish)
```fish
direnv hook fish | source
```
### After Configuration
Restart your shell:
```bash
exec $SHELL
```
## .envrc File Basics
### Creating an .envrc
```bash
# In your project directory
touch .envrc
# Edit with your preferred editor
vim .envrc
```
### Basic Syntax
```bash
# Export environment variables
export NODE_ENV=development
export API_URL=http://localhost:3000
export DATABASE_URL=postgres://localhost/myapp
# The export keyword is required for direnv to capture variables
```
### Allowing the .envrc
```bash
# Allow current directory
direnv allow
# Allow specific path
direnv allow /path/to/project
# Deny/revoke access
direnv deny
```
## Standard Library Functions
direnv includes a powerful stdlib. Always prefer stdlib functions over manual exports.
### PATH Management
```bash
# Prepend to PATH (safer than manual export)
PATH_add bin
PATH_add node_modules/.bin
PATH_add scripts
# Add to arbitrary path-like variable
path_add PYTHONPATH lib
path_add LD_LIBRARY_PATH /opt/lib
# Remove from PATH
PATH_rm "*/.git/bin"
```
### Environment File Loading
```bash
# Load .env file (current directory)
dotenv
# Load specific file
dotenv .env.local
# Load only if exists (no error)
dotenv_if_exists .env.local
dotenv_if_exists .env.${USER}
# Source another .envrc
source_env ../.envrc
source_env /path/to/.envrc
# Search upward and source parent .envrc
source_up
# Source if exists
source_env_if_exists .envrc.local
```
### Language Layouts
**Node.js:**
```bash
# Adds node_modules/.bin to PATH
layout node
```
**Python:**
```bash
# Creates virtualenv in .direnv/python-X.X/
layout python
# Use specific Python version
layout python python3.11
# Shortcut for Python 3
layout python3
# Use Pipenv (reads from Pipfile)
layout pipenv
```
**Ruby:**
```bash
# Sets GEM_HOME to project directory
layout ruby
```
**Go:**
```bash
# Modifies GOPATH and adds bin to PATH
layout go
```
**Perl:**
```bash
# Configures local::lib environment
layout perl
```
### Nix Integration
```bash
# Load nix-shell environment
use nix
# With specific file
use nix shell.nix
# Load from Nix flake
use flake
# Load specific flake
use flake "nixpkgs#hello"
use flake ".#devShell"
```
**For better Nix Flakes support, install nix-direnv:**
```bash
# Provides faster, cached use_flake implementation
# https://github.com/nix-community/nix-direnv
```
### Version Managers
```bash
# rbenv
use rbenv
# Node.js (with fuzzy version matching)
use node 18
use node 18.17.0
# Reads from .nvmrc if version not specified
use node
# Julia
use julia 1.9
```
### Validation
```bash
# Require environment variables (errors if missing)
env_vars_required API_KEY DATABASE_URL SECRET_KEY
# Enforce minimum direnv version
direnv_version 2.32.0
# Check git branch
if on_git_branch main; then
export DEPLOY_ENV=production
fi
if on_git_branch develop; then
export DEPLOY_ENV=staging
fi
```
### File Watching
```bash
# Reload when files change
watch_file package.json
watch_file requirements.txt
watch_file .tool-versions
watch_file config/*.yaml
# Watch entire directory
watch_dir config
watch_dir migrations
```
### Utility Functions
```bash
# Check if command exists
if has docker; then
export DOCKER_HOST=unix:///var/run/docker.sock
fi
# Expand relative path to absolute
expand_path ./bin
# Find file searching upward
find_up package.json
# Enable strict mode (exit on errors)
strict_env
# Load prefix (configures CPATH, LD_LIBRARY_PATH, etc.)
load_prefix /usr/local/custom
# Load remote script with integrity verification
source_url https://example.com/script.sh "sha256-HASH..."
```
## Best Practices
### Recommended .envrc Template
```bash
#!/usr/bin/env bash
# .envrc - Project environment configuration
# Enforce direnv version for team consistency
direnv_version 2.32.0
# Load .env if exists
dotenv_if_exists
# Load local overrides (not committed to git)
source_env_if_exists .envrc.local
# Language-specific layout
layout node # or: layout python3
# Add project bin directories
PATH_add bin
PATH_add scripts
# Development defaults
export NODE_ENV="${NODE_ENV:-development}"
export LOG_LEVEL="${LOG_LEVEL:-debug}"
# Watch for dependency changes
watch_file package.json
watch_file .nvmrc
```
### Git Configuration
**.gitignore:**
```gitignore
# Environment files with secrets
.env
.env.local
.envrc.local
# direnv virtualenv/cache
.direnv/
```
**Commit to repository:**
- `.envrc` (base configuration, no secrets)
- `.env.example` (template for team members)
### Secrets Management
**Never commit secrets.** Use environment variable fallbacks:
```bash
# .envrc (committed)
export DATABASE_URL="${DATABASE_URL:-postgres://localhost/dev}"
export API_KEY="${API_KEY:-}"
# Validate required secrets
env_vars_required API_KEY
# .envrc.local (gitignored)
export DATABASE_URL="postgres://user:secret@prod/app"
export API_KEY="actual-secret-key"
```
### Layered Configuration
```bash
# ~/projects/.envrc (global dev settings)
export EDITOR=vim
# ~/projects/api/.envrc
source_up
export API_PORT=3000
# ~/projects/api/feature/.envrc
source_up
export FEATURE_FLAG=true
```
### Project Structure
```
my-project/
├── .envrc # Base environment (committed)
├── .envrc.local # Local overrides (gitignored)
├── .env # Environment variables (gitignored)
├── .env.example # Template for team (committed)
└── .direnv/ # direnv cache (gitignored)
```
## Custom Extensions
Create `~/.config/direnv/direnvrc` for custom functions:
```bash
#!/usr/bin/env bash
# ~/.config/direnv/direnvrc
# Custom function: Use specific Kubernetes context
use_kubernetes() {
local context="${1:-default}"
export KUBECONFIG="${HOME}/.kube/config"
kubectl config use-context "$context" >/dev/null 2>&1
log_status "kubernetes context: $context"
}
# Custom function: Load from AWS Secrets Manager
use_aws_secrets() {
local secret_name="$1"
local region="${2:-us-east-1}"
eval "$(aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--region "$region" \
--query SecretString \
--output text | jq -r 'to_entries | .[] | "export \(.key)=\"\(.value)\""')"
log_status "loaded secrets from: $secret_name"
}
# Custom function: Use asdf versions from .tool-versions
use_asdf() {
watch_file .tool-versions
source_env "$(asdf direnv local)"
}
```
Usage in `.envrc`:
```bash
use kubernetes dev-cluster
use aws_secrets myapp/dev
use asdf
```
## Commands Reference
| Command | Description |
|---------|-------------|
| `direnv allow` | Allow the current .envrc |
| `direnv deny` | Revoke .envrc access |
| `direnv reload` | Force reload environment |
| `direnv status` | Show current status |
| `direnv dump` | Dump current environment |
| `direnv edit` | Open .envrc in editor |
| `direnv version` | Show direnv version |
## Troubleshooting
### Environment Not Loading
```bash
# Check status
direnv status
# Force reload
direnv reload
# Re-allow .envrc
direnv allow
# Check if hook is installed
echo $DIRENV_DIR
```
### Shell Hook Issues
1. Verify hook is in shell config file
2. Ensure it's at the END of the file
3. Restart shell completely: `exec $SHELL`
4. Check for errors: `direnv hook zsh`
### Performance Issues
```bash
# Show what's being evaluated
direnv show_dump
# For Nix, use nix-direnv for caching
# https://github.com/nix-community/nix-direnv
```
### Debugging
```bash
# Verbose output
export DIRENV_LOG_FORMAT='%s'
# Show exported variables
direnv dump | jq
# Test .envrc syntax
bash -n .envrc
```
## IDE Integration
### VS Code
Install [direnv extension](https://marketplace.visualstudio.com/items?itemName=mkhl.direnv) for automatic environment loading in integrated terminal.
### JetBrains
Install [direnv integration plugin](https://plugins.jetbrains.com/plugin/15285-direnv-integration).
### Neovim
Use [direnv.vim](https://github.com/direnv/direnv.vim) or configure with lua.
## Common Patterns
### Development vs Production
```bash
# .envrc
export NODE_ENV="${NODE_ENV:-development}"
if [[ "$NODE_ENV" == "development" ]]; then
export DEBUG=true
export LOG_LEVEL=debug
else
export DEBUG=false
export LOG_LEVEL=info
fi
```
### Multi-Service Projects (Monorepo)
```bash
# root/.envrc
export PROJECT_ROOT="$(pwd)"
export COMPOSE_PROJECT_NAME=myapp
# services/api/.envrc
source_up
export SERVICE_NAME=api
export SERVICE_PORT=3000
# services/web/.envrc
source_up
export SERVICE_NAME=web
export SERVICE_PORT=8080
```
### Docker Integration
```bash
# .envrc
export COMPOSE_FILE=docker-compose.yml
export COMPOSE_PROJECT_NAME="${PWD##*/}"
if has docker-compose; then
export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}"
fi
# Add Docker bin for containers that install CLI tools
PATH_add .docker/bin
```
## References
- [Official Documentation](https://direnv.net/)
- [Installation Guide](https://direnv.net/docs/installation.html)
- [Shell Hook Setup](https://direnv.net/docs/hook.html)
- [Standard Library Reference](https://direnv.net/man/direnv-stdlib.1.html)
- [nix-direnv](https://github.com/nix-community/nix-direnv)
- [Homebrew Formula](https://formulae.brew.sh/formula/direnv)
---
## Gotchas
- **`direnv` reloads on `cd` but NOT on `.envrc` edit unless you re-enter the dir:** A change to `.envrc` looks applied (no error) but isn't until `cd .` or `direnv reload`. Use `watch_file` on `.envrc` itself if editing in-place.
- **`direnv allow` is keyed on file content hash, not path:** Renaming `.envrc.dev` to `.envrc` and back keeps allow state. But editing a single byte revokes — even a stray trailing newline from saving in a new editor.
- **`layout python` creates `.direnv/python-X.Y/` tied to the host's python version:** A python upgrade silently breaks the venv. Pin via `layout python python3.11` or rebuild with `rm -rf .direnv && direnv reload`.
- **`source_up` searches ancestors, not just immediate parent:** A stray `.envrc` in `~/` or `~/Projects/` leaks into every subdirectory project. Audit with `direnv status` from deep in a tree to see all chained files.
- **Hook placement order matters in `.zshrc`:** Place `eval "$(direnv hook zsh)"` AFTER prompt/p10k setup. Earlier and `precmd` hooks fire before prompt is ready — direnv output gets eaten by the prompt redraw.
- **`PATH_add bin` adds project-relative path that breaks when scripts `cd` elsewhere:** The PATH entry is absolute (resolved at load), so `cd /tmp && project-binary` still works — but binaries that read `$PWD/bin` at runtime do not.