| Crates.io | dotenvage |
| lib.rs | dotenvage |
| version | 0.1.11 |
| created_at | 2025-10-30 14:09:32.154845+00 |
| updated_at | 2026-01-17 03:43:19.000498+00 |
| description | Dotenv with age encryption: encrypt/decrypt secrets in .env files |
| homepage | https://github.com/dataroadinc/dotenvage |
| repository | https://github.com/dataroadinc/dotenvage |
| max_upload_size | |
| id | 1908284 |
| size | 214,552 |
Dotenv with age encryption: encrypt/decrypt secrets in .env files.
The key advantage: With encrypted secrets, you can safely commit
all your .env* files to version control - including production
configs, user-specific settings, and files with sensitive data. No
more .gitignore juggling or secret management headaches.
The fastest way to install pre-built binaries:
cargo install cargo-binstall
cargo binstall dotenvage
Build from source (slower, requires Rust toolchain):
cargo install dotenvage
Download pre-built binaries from GitHub Releases:
dotenvage-x86_64-unknown-linux-gnu.zipdotenvage-aarch64-unknown-linux-gnu.zipdotenvage-x86_64-apple-darwin.zipdotenvage-aarch64-apple-darwin.zipdotenvage-x86_64-pc-windows-msvc.zipdotenvage-aarch64-pc-windows-msvc.zip# Generate a key
dotenvage keygen
# Encrypt sensitive values in .env.local
dotenvage encrypt .env.local
# Edit (decrypts in editor, re-encrypts on save)
dotenvage edit .env.local
# Set a value (auto-encrypts if key name matches patterns)
dotenvage set FLY_API_TOKEN=abc123 --file .env.local
# Get a decrypted value (searches .env then .env.local)
dotenvage get FLY_API_TOKEN
# List all variables from all .env* files (merged in standard order)
dotenvage list
# List with lock icons (🔒 = encrypted)
dotenvage list --verbose
# List in plain ASCII format (no icons, just variable names)
dotenvage list --plain
# List in JSON format
dotenvage list --json
# List in JSON with values
dotenvage list --json --verbose
# List from a specific file only
dotenvage list --file .env.local
# Dump all decrypted env vars (merges all .env* files with layering)
dotenvage dump
# Dump a specific file
dotenvage dump --file .env.local
# Dump with bash-compliant escaping (for values with $, `, etc.)
dotenvage dump --bash
# Dump in GNU Make format (VAR := value with Make-safe escaping)
dotenvage dump --make
# Dump with export prefix for bash sourcing (auto-enables --bash)
dotenvage dump --export
# Source in bash (loads all env vars into current shell)
eval "$(dotenvage dump --export)"
# or
source <(dotenvage dump --export)
# Use in Makefile (GNU Make) - secure, no temp file created
# $(eval $(shell dotenvage dump --make-eval))
# export
#
# In recipes, use: $$DATABASE_URL (shell env var)
# Not: $(DATABASE_URL) (Make variable expansion)
use dotenvage::{SecretManager, EnvLoader};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load env files with auto-decryption
EnvLoader::new()?.load()?;
// Get all variable names (functional style)
let vars = EnvLoader::new()?.get_all_variable_names()?.join(", ");
// Encrypt and decrypt values
let manager = SecretManager::generate()?;
let enc = manager.encrypt_value("secret")?;
let dec = manager.decrypt_value(&enc)?;
Ok(())
}
One of dotenvage's key features is automatic file layering -
multiple .env* files are loaded and merged with a clear precedence
order. Later files override values from earlier files.
Files are loaded using a flexible power-set algorithm that generates all possible combinations of ENV, OS, ARCH, and USER. This allows any combination you need without being constrained by a fixed hierarchy.
Key principle: All multi-part file names use dots as separators only (not dashes), ensuring unambiguous parsing.
Files are loaded in specificity order (later overrides earlier):
.env - Base configuration (always first).env.<ENV>, .env.<OS>, .env.<ARCH>,
.env.<USER>.env.<ENV>.<OS>, .env.<ENV>.<ARCH>,
.env.<ENV>.<USER>, etc..env.<ENV>.<OS>.<ARCH>,
.env.<ENV>.<OS>.<USER>, etc..env.<ENV>.<OS>.<ARCH>.<USER> (most
specific).env.pr-<NUMBER> - PR-specific (GitHub Actions only, always
last)All files can be safely committed to git since secrets are encrypted.
With ENV=prod, OS=linux, ARCH=amd64, USER=alice, these files
would be loaded (in order):
.env.env.prod.env.linux.env.amd64.env.alice.env.prod.linux.env.prod.amd64.env.prod.alice.env.linux.amd64.env.linux.alice.env.amd64.alice.env.prod.linux.amd64.env.prod.linux.alice.env.prod.amd64.alice.env.linux.amd64.alice.env.prod.linux.amd64.aliceYou only need to create the files you use - the loader checks which exist.
| Placeholder | Environment Variables (priority order) | Default / Notes |
|---|---|---|
<ENV> |
DOTENVAGE_ENV, EKG_ENV, VERCEL_ENV, NODE_ENV |
Defaults to local |
<OS> |
DOTENVAGE_OS, EKG_OS, CARGO_CFG_TARGET_OS, TARGET, RUNNER_OS |
Runtime detection if not set |
<ARCH> |
DOTENVAGE_ARCH, EKG_ARCH, CARGO_CFG_TARGET_ARCH, TARGET, TARGETARCH, TARGETPLATFORM, RUNNER_ARCH |
None if not detected |
<USER> |
DOTENVAGE_USER, EKG_USER, GITHUB_ACTOR, GITHUB_TRIGGERING_ACTOR, GITHUB_REPOSITORY_OWNER, USER, USERNAME |
System username |
<PR_NUMBER> |
PR_NUMBER, GITHUB_REF |
GitHub Actions only |
The <OS> placeholder supports these canonical values (with
normalization):
| Canonical | File Example | Aliases (normalized to canonical) |
|---|---|---|
linux |
.env.prod.linux |
- |
macos |
.env.prod.macos |
darwin, osx |
windows |
.env.prod.windows |
win32, win |
freebsd |
.env.prod.freebsd |
- |
openbsd |
.env.prod.openbsd |
- |
netbsd |
.env.prod.netbsd |
- |
android |
.env.prod.android |
- |
ios |
.env.prod.ios |
- |
The <ARCH> placeholder supports these canonical values (with
normalization):
| Canonical | File Example | Aliases (normalized to canonical) |
|---|---|---|
amd64 |
.env.prod.amd64 |
x64, x86_64 |
arm64 |
.env.prod.arm64 |
aarch64 |
arm |
.env.prod.arm |
armv7, armv7l, armhf |
i386 |
.env.prod.i386 |
i686, x86 |
riscv64 |
.env.prod.riscv64 |
riscv64gc |
ppc64le |
.env.prod.ppc64le |
powerpc64le |
s390x |
.env.prod.s390x |
- |
Note: Custom architecture values (e.g., docker-s3) are passed
through as lowercase and can include dashes within the value itself
(e.g., .env.prod.docker-s3), but dots remain the separator between
file name parts.
Given these files:
# .env - Base config (safe to commit)
DATABASE_URL=postgres://localhost/dev
API_KEY=public_key
# .env.local - Local overrides (safe to commit with encryption)
DATABASE_URL=postgres://localhost/mydb
SECRET_TOKEN=age[...] # encrypted, safe to commit!
Running dotenvage dump produces:
# .env
API_KEY=public_key
DATABASE_URL=postgres://localhost/dev
# .env.local
DATABASE_URL=postgres://localhost/mydb
SECRET_TOKEN=decrypted_value
Running dotenvage dump --export produces (note: --export
automatically enables bash-compliant escaping):
# .env
export API_KEY=public_key
export DATABASE_URL=postgres://localhost/dev
# .env.local
export DATABASE_URL=postgres://localhost/mydb
export SECRET_TOKEN=decrypted_value
When using --bash or --export (which auto-enables --bash),
special bash characters are properly escaped:
# Without --bash (simple .env format)
PASSWORD=my$ecret
# With --bash (bash-safe escaping)
PASSWORD="my\$ecret"
This ensures values with $, `, \, !, and other bash
special characters are safely preserved when sourced.
Use --make-eval to securely load variables directly into Make
without creating temporary files:
# Makefile example - secure, no temp file with secrets
$(eval $(shell dotenvage dump --make-eval))
export
.PHONY: deploy
deploy:
@echo "Deploying to $$DATABASE_URL"
@echo "Using API key: $$API_KEY"
Security Note: --make-eval outputs $(eval ...) statements that
are processed directly by Make, avoiding the security risk of writing
decrypted secrets to temporary files.
Important: Access variables as $$VAR (environment variables) in
recipes, not $(VAR) (Make variable expansion). The export
directive makes all variables available to recipe shells as
environment variables, where special characters like $ are properly
preserved.
Alternative: If you need the Make format for other purposes,
--make outputs VAR := value format (but creates a file if
redirected).
This layering system allows you to:
.env* files to version control - secrets are
encrypted.env.production, .env.staging).env.local.alice) without
conflicts.env.local.arm64)Keys are discovered in this priority order:
DOTENVAGE_AGE_KEY env var (full identity string)AGE_KEY env var (full identity string)EKG_AGE_KEY env var (for EKG project compatibility)AGE_KEY_NAME from .env → key file at
$XDG_STATE_HOME/{AGE_KEY_NAME}.key~/.local/state/{CARGO_PKG_NAME}/dotenvage.keyFor multi-project setups, configure in .env:
# .env (committed, not secret)
AGE_KEY_NAME=myproject/myapp
Key stored at: ~/.local/state/myproject/myapp.key
$XDG_STATE_HOME~/.local/state$XDG_CONFIG_HOME / ~/.config (legacy)Set DOTENVAGE_AGE_KEY, AGE_KEY, or EKG_AGE_KEY in CI secrets:
env:
DOTENVAGE_AGE_KEY: ${{ secrets.AGE_KEY }}
Contributions are welcome! Please see CONTRIBUTING.md for setup instructions and guidelines.
Licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. See LICENSE for details.