#encryption #dotenv #cli

bin+lib dotenvx

A secure environment variable management tool with built-in encryption

4 releases

0.2.2 Nov 26, 2025
0.2.1 Nov 26, 2025
0.2.0 Nov 26, 2025
0.1.0 Nov 26, 2025

#806 in Cryptography

MIT/Apache

91KB
2K SLoC

dotenvx

CI codecov Crates.io License: MIT OR Apache-2.0

A secure environment variable management tool with built-in encryption, written in Rust. This is a complete reimplementation of dotenvx with performance improvements and memory safety guarantees.

Features

  • 🔐 Built-in Encryption: ECIES encryption using secp256k1 (same curve as Bitcoin)
  • 🚀 High Performance: 10-100x faster than the Node.js version
  • 🔒 Memory Safe: Written in Rust with zero unsafe code
  • 🌍 Cross-Platform: Works on Linux, macOS, and Windows
  • 📦 Small Binary: Self-contained binaries (5-10 MB)
  • 🔑 Secure Key Management: Public/private keypair generation and management
  • 🔄 Variable Expansion: Supports ${VAR:-default} syntax
  • 💻 Command Substitution: Execute shell commands in .env files
  • 🎯 Zero Dependencies: No runtime dependencies required

Installation

Homebrew (macOS)

brew tap fabianopinto/tap
brew install dotenvx

From crates.io

cargo install dotenvx

From source

git clone https://github.com/fabianopinto/dotenvx
cd dotenvx
cargo install --path .

Pre-built binaries

Download pre-built binaries from the releases page.

Quick Start

1. Generate a keypair

dotenvx keypair

This generates a public/private keypair for encryption.

2. Encrypt your .env file

# Create a .env file
echo "DATABASE_PASSWORD=super_secret" > .env

# Encrypt it
dotenvx encrypt

Your .env file now contains encrypted values:

#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/            public-key encryption for .env files          /
#/       [how it works](https://dotenvx.com/encryption)     /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e..."

DATABASE_PASSWORD="encrypted:BG8M6U+GKJGwpGA..."

The private key is stored in .env.keys (automatically added to .gitignore).

3. Run your application

dotenvx run -- your-command

Environment variables are automatically decrypted and injected.

Usage

Commands

keypair - Generate a new keypair

dotenvx keypair

Output:

DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e..."
DOTENV_PRIVATE_KEY="ec9e80073d7ace817d35acb8b7293cbf..."

encrypt - Encrypt environment variables

# Encrypt default .env file
dotenvx encrypt

# Encrypt specific file
dotenvx encrypt -f .env.production

# Encrypt specific keys only
dotenvx encrypt -K API_KEY -K DATABASE_PASSWORD

# Exclude certain keys
dotenvx encrypt -e DEBUG -e LOG_LEVEL

decrypt - Decrypt environment variables

# Decrypt default .env file
dotenvx decrypt

# Decrypt specific file
dotenvx decrypt -f .env.production

set - Set an environment variable (encrypted by default)

# Set encrypted variable
dotenvx set API_KEY "sk_live_123456"

# Set plain text variable
dotenvx set DEBUG "true" --plain

# Set in specific file
dotenvx set DATABASE_URL "postgres://..." -f .env.production

get - Get an environment variable value

# Get specific variable
dotenvx get API_KEY

# Get all variables
dotenvx get

# From specific file
dotenvx get DATABASE_URL -f .env.production

ls - List all .env files

# List in current directory
dotenvx ls

# List in specific directory
dotenvx ls /path/to/project

run - Run command with environment variables

# Run with default .env
dotenvx run -- node server.js

# Run with specific files (last wins)
dotenvx run -f .env -f .env.local -- python app.py

# Override existing environment variables
dotenvx run --overload -- ./my-app

printenv - Print environment variables for shell evaluation

# Print all variables in bash format (default)
dotenvx printenv

# Print from specific file
dotenvx printenv -f .env.production

# Print from multiple files (last wins)
dotenvx printenv -f .env -f .env.local

# Use with eval to set environment variables
eval "$(dotenvx printenv)"

# Output in different formats
dotenvx printenv --format bash
dotenvx printenv --format fish
dotenvx printenv --format powershell
dotenvx printenv --format json

The printenv command is quiet by default, making it suitable for shell evaluation:

# Bash/Zsh
eval "$(dotenvx printenv)"

# Fish
dotenvx printenv --format fish | source

# PowerShell
Invoke-Expression (dotenvx printenv --format powershell)

How It Works

Encryption Flow

  1. Key Generation: Generate a secp256k1 keypair (same curve as Bitcoin)
  2. Encryption: Values are encrypted using ECIES (Elliptic Curve Integrated Encryption Scheme)
    • Ephemeral keypair is generated for each encryption
    • Shared secret is derived using ECDH
    • AES-256-GCM is used for symmetric encryption
  3. Storage:
    • Public key is stored in .env file
    • Private key is stored in .env.keys (gitignored)
    • Encrypted values are base64-encoded with encrypted: prefix

Decryption Flow

  1. Key Lookup: Find private key from .env.keys, environment variable, or custom path
  2. Decryption: Reverse the encryption process
  3. Injection: Set decrypted values as environment variables

File Structure

project/
├── .env                    # Public key + encrypted values (committed)
├── .env.keys              # Private keys (gitignored)
├── .env.production        # Production environment
└── .env.keys              # Production keys (deploy separately)

Library Usage

Add to your Cargo.toml:

[dependencies]
dotenvx = "0.1"

Example:

use dotenvx::crypto::{Keypair, encrypt, decrypt};
use dotenvx::parser::DotenvParser;

fn main() {
    // Generate keypair
    let keypair = Keypair::generate();

    // Encrypt a value
    let encrypted = encrypt("secret", &keypair.public_key()).unwrap();

    // Decrypt a value
    let decrypted = decrypt(&encrypted, &keypair.private_key()).unwrap();

    // Parse .env file
    let mut parser = DotenvParser::new();
    parser.parse_with_processing("KEY=value\nURL=$KEY/path").unwrap();
}

Configuration Files

.env file format

# Comments are supported
KEY=value
export EXPORTED_KEY=value

# Quotes (single, double, or none)
SINGLE='value'
DOUBLE="value"
UNQUOTED=value

# Variable expansion
DATABASE_URL=postgres://${DB_HOST:-localhost}/${DB_NAME}

# Command substitution
CURRENT_USER=$(whoami)
BUILD_TIME=$(date +%s)

# Encrypted values
API_KEY="encrypted:BG8M6U+GKJGwpGA42ml2erb9..."

# Public key (added automatically)
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236..."

.env.keys file format

#/------------------!DOTENV_PRIVATE_KEYS!-------------------/
#/ private decryption keys. DO NOT commit to source control /
#/     [how it works](https://dotenvx.com/encryption)       /
#/----------------------------------------------------------/

# .env
DOTENV_PRIVATE_KEY=ec9e80073d7ace817d35acb8b7293cbf...

# .env.production
DOTENV_PRIVATE_KEY_PRODUCTION=1fc1cafa954a7a2bf0a6fbff...

Security Best Practices

  1. Never commit .env.keys - Add to .gitignore immediately
  2. Rotate keys regularly - Generate new keypairs periodically
  3. Use different keys per environment - Separate development/staging/production
  4. Store production keys securely - Use secret management systems
  5. Audit encrypted files - Review what's encrypted regularly

Performance

Compared to the Node.js version:

  • Startup time: ~50ms vs ~500ms (10x faster)
  • Encryption: ~100x faster for bulk operations
  • Memory usage: ~2MB vs ~50MB (25x smaller)
  • Binary size: 6MB vs 50MB+ with Node.js

Development

Prerequisites

  • Rust 1.70+ (install via rustup)

Build

cargo build --release

Test

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test
cargo test test_encrypt_decrypt

Lint

# Format code
cargo fmt

# Lint
cargo clippy -- -D warnings

Coverage

cargo install cargo-tarpaulin
cargo tarpaulin --out Html

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

Licensed under either of:

at your option.

Acknowledgments

  • Original dotenvx by @motdotla
  • secp256k1 Rust implementation
  • Bitcoin for the secp256k1 curve specification

Dependencies

~19–36MB
~368K SLoC