Originally published on 2026-01-27
Original article (Japanese): direnvとdotenvx-rsの組み合わせ: 自動ロードと暗号化を両立させる設定パターン
Recently, I wanted to commit the project's .env file to Git and tried encrypting it with dotenvx-rs, but I was about to give up because it was cumbersome to type dotenvx run every time. I thought, "Wouldn't it be possible to automate this by combining it with direnv?" After trying it out, I was able to create a very comfortable configuration, and I’d like to share it.
Role Distribution Between direnv and dotenvx-rs
First, let's clarify the areas where each tool excels.
| Tool | Strong Areas | Weak Areas |
|---|---|---|
| direnv | Automatic loading of environment variables when changing directories | Encryption, validation |
| dotenvx-rs | Encryption/decryption of .env, signature verification |
Automatic loading (manual execution required) |
direnv uses the .envrc file to automatically switch the shell environment, but it does not support encrypted .env files. On the other hand, dotenvx-rs specializes in encryption and decryption but does not provide automatic loading when changing directories.
By combining these two, we can achieve the ideal workflow of "automatically decrypting the encrypted .env and loading it into environment variables when entering a directory."
What is dotenvx-rs?
dotenvx-rs is a CLI tool that reimplements the original dotenvx in Rust. Its main features are as follows:
- Lightweight: The binary size is about 4MB (the Node.js version is about 50MB)
-
Global Key Store: Saves the secret key in
$HOME/.dotenvx/.env.keys.jsonto prevent scanning by AI agents -
Signature Feature: Detects tampering of the
.envfile (secp256k1 signature) -
direnv/mise Integration: Supports automatic loading via
.envrc
Existing articles introducing dotenvx, such as "dotenvx: A Tool for Comfortable Environment Variable Management," dealt with the original Node.js version. This article will focus on the Rust version's feature of "direnv integration."
Basic Configuration Pattern
1. Installing dotenvx-rs
# For Homebrew
brew install linux-china/tap/dotenvx-rs
# For cargo-binstall
cargo binstall dotenvx-rs
# Alternatively, download directly from GitHub releases
# https://github.com/linux-china/dotenvx-rs/releases
2. Encrypting the .env File
Run the following in your project directory:
# Initialization (generating key pair and creating .env file)
dotenvx init
When you run dotenvx init, the following will be generated:
- A
.envfile (including the public keyDOTENV_PUBLIC_KEY) - The secret key saved in
$HOME/.dotenvx/.env.keys.json
Next, encrypt and add environment variables:
# Encrypt environment variables and add to .env
dotenvx set --encrypt DATABASE_URL "postgresql://user:pass@localhost/db"
dotenvx set --encrypt API_KEY "secret-api-key"
Note: By explicitly specifying the --encrypt option, the values will be encrypted. The encrypted values will be saved with the encrypted: prefix. The README states that values are automatically encrypted with dotenvx set, but in my tests, if I didn't include --encrypt, they were saved in plaintext. I recommend explicitly specifying the option.
When you check the contents of the .env file, it looks like this:
# .env (after encryption)
DOTENV_PUBLIC_KEY="02b4972559803fa3c2464e93858f80c3a4c86f046f725329f8975e007b393dc4f0"
DATABASE_URL=encrypted:BNexEwjKwt87k9aEgaSng1JY6uW8OkwMYEFTwEy/xyz...
API_KEY=encrypted:BGcRf5bK/mChGEqT1MZ8hUbMm3hhtuW9NVGkHtl7KRwq...
3. Setting Up .envrc
Create a .envrc file in the project root:
# .envrc
eval $( dotenvx decrypt --stdout --format shell )
With this single line, direnv will automatically call dotenvx-rs when you enter the directory, decrypting the encrypted .env and loading it as environment variables.
4. Allowing direnv
For the first time, allow direnv to execute the .envrc:
direnv allow
That's it for the setup. Now, when you enter the directory, the environment variables will be loaded automatically.
Verifying Functionality
Let's check if the environment variables are loaded:
# Move to the project directory
cd /path/to/project
# direnv automatically executes .envrc
# direnv: loading .envrc
# direnv: export +DATABASE_URL +API_KEY
# Check the environment variables
echo $DATABASE_URL
# postgresql://user:pass@localhost/db
echo $API_KEY
# secret-api-key
When you leave the directory, the environment variables are automatically unloaded:
cd ..
# direnv: unloading
echo $DATABASE_URL
# (empty)
Switching Between Multiple Environments
dotenvx-rs can manage multiple environments (dev, prod, test, etc.) using the profile feature.
Creating .env Files for Different Profiles
# Initialize the .env file for the development environment
dotenvx init -f .env.dev
dotenvx set --encrypt -f .env.dev DATABASE_URL "postgresql://localhost/dev_db"
# Initialize the .env file for the production environment
dotenvx init -f .env.prod
dotenvx set --encrypt -f .env.prod DATABASE_URL "postgresql://prod-server/prod_db"
Switching Profiles in .envrc
# .envrc
# Specify the profile using an environment variable
export DOTENVX_PROFILE="dev"
eval $( dotenvx decrypt --stdout --format shell -f .env.${DOTENVX_PROFILE} )
Alternatively, if you want to use a fixed profile for each project:
# .envrc (for production environment)
eval $( dotenvx decrypt --stdout --format shell -f .env.prod )
Security Advantages
This configuration offers the following security advantages:
1. Protecting the Secret Key from AI Agents
dotenvx-rs saves the secret key in $HOME/.dotenvx/.env.keys.json. This reduces the risk of AI code completion tools like Cursor and GitHub Copilot scanning the project directory and reading the secret key.
In the original dotenvx, the secret key was saved as a .env.keys file within the project. If an AI agent reads the entire project, this secret key could also be included.
2. Signature Verification of the .env File
dotenvx-rs can sign the .env file:
dotenvx encrypt --sign
This allows you to verify that the .env file has not been tampered with. The signature uses secp256k1 elliptic curve cryptography, ensuring the integrity of the file.
3. Committing the Encrypted .env to Git
The encrypted .env file can be safely committed to a Git repository. Since the secret key is stored separately (in $HOME/.dotenvx/.env.keys.json), even if the repository is leaked, the secret information remains protected.
# .gitignore
.env.keys # Exclude the old dotenvx secret key file (for safety)
# The .env file is encrypted, so it can be committed
git add .env .envrc
git commit -m "Add encrypted environment variables"
Trade-offs and Considerations
This configuration comes with several trade-offs.
1. Initial Setup Effort
When team members clone the project for the first time, they need to follow these steps:
- Install dotenvx-rs
- Share the secret key (via the
DOTENV_PRIVATE_KEYenvironment variable or by adding it to$HOME/.dotenvx/.env.keys.json) - Run
direnv allow
The method for sharing the secret key should be determined according to the team's operational policy. For example:
- 1Password/Bitwarden: Store the secret key in a team-shared vault
-
CI/CD Environment Variables: Set
DOTENV_PRIVATE_KEYin GitHub Secrets, etc. - Manual Sharing: Share as an encrypted message via Slack, etc.
2. direnv Performance
While dotenvx-rs is fast, the decryption process runs every time you change directories, which may introduce a slight delay. In practice, this is around several tens of milliseconds, but it could be noticeable if you frequently switch directories.
3. Overwriting Environment Variables
direnv overwrites existing environment variables. If there are name conflicts with environment variables set system-wide, it could lead to unintended behavior.
# If the PATH is set system-wide
export PATH="/usr/local/bin:$PATH"
# Overwriting PATH in .envrc will lose the system PATH
# Mitigation: Append to PATH in .envrc
export PATH="./bin:$PATH"
Combining with mise
mise is a version management and environment variable management tool that can serve as an alternative to direnv. You can integrate dotenvx-rs with mise as well.
Setting Up mise.toml
# mise.toml
[env]
_.source = "scripts/env.sh"
Creating scripts/env.sh
#!/bin/bash
# scripts/env.sh
eval $( dotenvx decrypt --stdout --format shell )
When using mise, environment variables are automatically loaded upon changing directories, just like with direnv. Since mise also integrates tool version management, it centralizes the management of Node.js and Python version switching along with environment variables.
Conclusion
By combining direnv and dotenvx-rs, you gain the following benefits:
- Convenience: Automatic loading of environment variables when changing directories
-
Security: Encryption and signature verification of the
.envfile - Protection Against AI Agents: Storing the secret key outside the project
On the other hand, you need to consider the initial setup effort and how to share the secret key. It is advisable to clarify operational policies in team development.
Personally, I feel this configuration strikes a good balance between convenience and security for local development environments. Especially when using AI code completion tools like Cursor, the fact that the secret key is stored outside the project is reassuring.
Initially, I thought that achieving both encryption and automatic loading would be cumbersome, but it turned out to be a pleasant surprise that it could be accomplished with just one additional line in .envrc. If you're interested, I encourage you to give it a try.
Top comments (0)