dotenvage

Crates.iodotenvage
lib.rsdotenvage
version0.1.11
created_at2025-10-30 14:09:32.154845+00
updated_at2026-01-17 03:43:19.000498+00
descriptionDotenv with age encryption: encrypt/decrypt secrets in .env files
homepagehttps://github.com/dataroadinc/dotenvage
repositoryhttps://github.com/dataroadinc/dotenvage
max_upload_size
id1908284
size214,552
Jacobus Geluk (jgeluk)

documentation

https://docs.rs/dotenvage

README

dotenvage

Crates.io Documentation CI License: MIT Downloads

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.

  • Selective encryption of sensitive keys
  • Uses age (X25519) for modern encryption
  • Library + CLI
  • CI-friendly (supports key via env var)
  • Automatic file layering with precedence rules

Installation

Using cargo-binstall (Recommended)

The fastest way to install pre-built binaries:

cargo install cargo-binstall
cargo binstall dotenvage

Using cargo install

Build from source (slower, requires Rust toolchain):

cargo install dotenvage

Manual Installation

Download pre-built binaries from GitHub Releases:

  • Linux (x86_64): dotenvage-x86_64-unknown-linux-gnu.zip
  • Linux (ARM64): dotenvage-aarch64-unknown-linux-gnu.zip
  • macOS (Intel): dotenvage-x86_64-apple-darwin.zip
  • macOS (Apple Silicon): dotenvage-aarch64-apple-darwin.zip
  • Windows (x86_64): dotenvage-x86_64-pc-windows-msvc.zip
  • Windows (ARM64): dotenvage-aarch64-pc-windows-msvc.zip

Usage

# 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)

Library

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(())
}

File Layering

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.

Loading Order

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):

  1. .env - Base configuration (always first)
  2. Single-part patterns: .env.<ENV>, .env.<OS>, .env.<ARCH>, .env.<USER>
  3. Two-part combinations: .env.<ENV>.<OS>, .env.<ENV>.<ARCH>, .env.<ENV>.<USER>, etc.
  4. Three-part combinations: .env.<ENV>.<OS>.<ARCH>, .env.<ENV>.<OS>.<USER>, etc.
  5. Four-part combination: .env.<ENV>.<OS>.<ARCH>.<USER> (most specific)
  6. .env.pr-<NUMBER> - PR-specific (GitHub Actions only, always last)

All files can be safely committed to git since secrets are encrypted.

Example Combinations

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.alice

You only need to create the files you use - the loader checks which exist.

Placeholders

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

Supported Operating Systems

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 -

Supported Architectures

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.

Example

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

Bash-Compliant Escaping

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.

GNU Make Integration

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:

  • Commit ALL .env* files to version control - secrets are encrypted
  • Share environment-specific configs across the team (.env.production, .env.staging)
  • Provide user-specific overrides (.env.local.alice) without conflicts
  • Configure architecture-specific settings (.env.local.arm64)

Key Management

Keys are discovered in this priority order:

  1. DOTENVAGE_AGE_KEY env var (full identity string)
  2. AGE_KEY env var (full identity string)
  3. EKG_AGE_KEY env var (for EKG project compatibility)
  4. AGE_KEY_NAME from .env → key file at $XDG_STATE_HOME/{AGE_KEY_NAME}.key
  5. Default: ~/.local/state/{CARGO_PKG_NAME}/dotenvage.key

Project-Specific Keys

For multi-project setups, configure in .env:

# .env (committed, not secret)
AGE_KEY_NAME=myproject/myapp

Key stored at: ~/.local/state/myproject/myapp.key

XDG Base Directories

  • Prefers $XDG_STATE_HOME
  • Falls back to ~/.local/state
  • Or $XDG_CONFIG_HOME / ~/.config (legacy)

CI/CD

Set DOTENVAGE_AGE_KEY, AGE_KEY, or EKG_AGE_KEY in CI secrets:

env:
  DOTENVAGE_AGE_KEY: ${{ secrets.AGE_KEY }}

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for setup instructions and guidelines.

License

Licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. See LICENSE for details.

Commit count: 147

cargo fmt