The Problem
I needed an ADMIN_TOKEN available in my terminal for production monitoring commands. I didn’t want to:
- Export it inline before each command (clumsy, exposes secrets in LLM chat histories)
- Write wrapper scripts/aliases that set it (adds complexity)
- Set it globally in
~/.zshrc(always loaded, pollutes environment) - Store it in
.env(unused until explicitly loaded by an application or script)
I wanted project-specific secrets that auto-load when I enter the directory and disappear when I leave. That’s direnv.
What is direnv?
direnv auto-loads and unloads environment variables based on your current directory. It’s the standard solution for project-specific configuration like API keys and tokens.
Setup
# macOS
brew install direnv
# Add to ~/.zshrc (one-time)
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
source ~/.zshrcMy Workflow
- Create .envrc in project root:
cd my-project
# Note: this puts the secret in shell history - use an editor for real credentials
echo 'export ADMIN_TOKEN="my_super_secure_token"' > .envrc- Allow direnv to load it:
direnv allow- Use the variable:
curl -H "X-Admin-Token: $ADMIN_TOKEN" https://api.example.com
./scripts/preflight_check.sh # ADMIN_TOKEN automatically available- Leave directory - variable disappears:
cd ~
echo $ADMIN_TOKEN # (empty)direnv won’t auto-run untrusted .envrc files - you must explicitly direnv allow each one after reviewing its contents.
Always add .envrc and .env to .gitignore - never commit secrets.
Common Use Cases
# .envrc
# API keys and tokens
export OPENAI_API_KEY="sk-definitely_not_my_real_key"
# Database credentials
export DATABASE_URL="postgresql://admin:my_super_secure_password@localhost/mydb"
# App configuration
export FLASK_ENV="development"For a stronger approach that eliminates plaintext secrets entirely, see 1Password CLI for Developer Secrets - op read slots right into .envrc files.
Resources
- direnv
- direnv installation
- direnv stdlib (
layout python,dotenv,PATH_add, and more)