Skip to content
All Skills

Direnv

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.

Software Engineering|v1|Updated 7/14/2026|GitHub source
MCP get_skill({ skillId: "direnv-452d9734" })

Use this skill with your agent

Create a free account and connect via MCP

Get Started Free
# 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.
#broad-capability#devops#azure#kubernetes#productivity#backend#development

Related Skills

More skills in Software Engineering

Accessibility Standards

Comprehensive web accessibility standards based on WCAG 2.2 AA, with 38+ anti-patterns, legal enforcement context (EAA, ADA Title II), WAI-ARIA patterns, and framework-specific fixes for modern web frameworks and libraries.

#github-copilot#accessibilityMIT

Accord

Authoring unified specification packages across Business/Development/Design teams via staged elaboration (L0 Vision → L1 Requirements → L2 Team Detail → L3 Acceptance Criteria). No code. Use when authoring cross-team specs, building L0-L3 packages, or aligning Biz/Dev/Design on a single source of truth.

#broad-capability#developmentMIT

Acquire Codebase Knowledge

Use this skill when the user explicitly asks to map, document, or onboard into an existing codebase. Trigger for prompts like "map this codebase", "document this architecture", "onboard me to this repo", or "create codebase docs". Do not trigger for routine feature implementation, bug fixes, or narrow code edits unless the user asks for repository-level discovery.

#github-copilot#documentationMIT

Acreadiness Assess

Run the AgentRC readiness assessment on the current repository and produce a static HTML dashboard at reports/index.html. Wraps `npx github:microsoft/agentrc readiness` and hands off rendering to the @ai-readiness-reporter custom agent. Supports policies (--policy) for org-specific scoring. Use when asked to assess, audit, or score the AI readiness of a repo.

#github-copilot#planningMIT

Acreadiness Generate Instructions

Generate tailored AI agent instruction files via AgentRC instructions command. Produces .github/copilot-instructions.md (default, recommended for Copilot in VS Code) plus optional per-area .instructions.md files with applyTo globs for monorepos. Use after running /acreadiness-assess to close gaps in the AI Tooling pillar.

#github-copilot#skillMIT

Acreadiness Policy

Help the user pick, write, or apply an AgentRC policy. Policies customise readiness scoring by disabling irrelevant checks, overriding impact/level, setting pass-rate thresholds, or chaining org baselines with team overrides. Use when the user asks about strict mode, AI-only scoring, custom weights, CI gating, or wants org-wide standardisation.

#github-copilot#planningMIT