Skip to main content

EnvLoader

Struct EnvLoader 

Source
pub struct EnvLoader { /* private fields */ }
Expand description

Loads environment files with automatic decryption of encrypted values.

EnvLoader reads .env files in a specific order and automatically decrypts any encrypted values it encounters. It supports multiple environment variants and user-specific configuration files.

§Examples

use dotenvage::EnvLoader;

// Load from current directory
let loaded_files = EnvLoader::new()?.load()?;
println!("Loaded {} .env files", loaded_files.len());

// Now encrypted values are available via std::env::var
let api_key = std::env::var("API_KEY")?;

§File Loading Order

Files are loaded in the following order (later files override earlier ones):

  1. .env - Base configuration
  2. .env.<ENV> - Environment-specific
  3. .env.<ENV>.<ARCH> - Architecture-specific (if <ARCH> is set)
  4. .env.<USER> - User-specific overrides (if <USER> is set)
  5. .env.<ENV>.<USER> - User overrides for specific environment
  6. .env.<ENV>.<ARCH>.<USER> - User overrides for env+arch combo
  7. .env.<VARIANT> - Variant-specific (e.g., docker, kubernetes)
  8. .env.pr-<PR_NUMBER> - PR-specific (GitHub Actions only)

Note: Separators can be either . or - (e.g., .env.local or .env-local)

§Placeholders

The following placeholders are resolved from environment variables:

Implementations§

Source§

impl EnvLoader

Source

pub fn new() -> SecretsResult<Self>

Creates a new EnvLoader with a default SecretManager.

This will load the encryption key from standard locations: 0. Auto-discover AGE_KEY_NAME from .env or .env.local files

  1. DOTENVAGE_AGE_KEY environment variable (full identity string)
  2. AGE_KEY environment variable
  3. Key file at path determined by discovered AGE_KEY_NAME
  4. Default key file at XDG path (e.g., ~/.local/state/dotenvage/dotenvage.key)
§Errors

Returns an error if no encryption key can be found or loaded.

Source

pub fn with_manager(manager: SecretManager) -> Self

Creates an EnvLoader with a specific SecretManager.

Use this when you want to provide your own encryption key.

§Examples
use dotenvage::{
    EnvLoader,
    SecretManager,
};

let manager = SecretManager::generate()?;
let loader = EnvLoader::with_manager(manager);
let loaded_files = loader.load()?;
println!("Loaded {} .env files", loaded_files.len());
Source

pub fn load(&self) -> SecretsResult<Vec<PathBuf>>

Loads .env files from the current directory in standard order.

Decrypted values are loaded into the process environment and can be accessed via std::env::var().

§Errors

Returns an error if any file cannot be read or parsed, or if decryption fails for any encrypted value.

§Returns

Returns the list of file paths that were actually loaded, in load order.

§Examples
use dotenvage::EnvLoader;

let loaded_files = EnvLoader::new()?.load()?;
println!("Loaded {} .env files", loaded_files.len());
let secret = std::env::var("API_TOKEN")?;
Source

pub fn load_from_dir( &self, dir: impl AsRef<Path>, ) -> SecretsResult<Vec<PathBuf>>

Loads .env files from a specific directory using the same order as load.

This method implements dynamic dimension discovery: dimension configuration values (like NODE_ENV=production or VARIANT=docker) found in loaded files can cause additional files to be loaded.

The loading algorithm:

  1. Load .env first (if it exists)
  2. Discover dimension configs from loaded values and set in environment
  3. Iteratively:
    • Compute file paths based on current dimension values
    • Find next unloaded file in specificity order
    • If none, break
    • Load file, discover new dimensions, update environment
  4. Set all accumulated variables in the process environment

Files are never loaded twice - a HashSet tracks loaded paths.

§Returns

Returns the list of file paths that were actually loaded, in load order.

§Errors

Returns an error if any file cannot be read or parsed, or if decryption fails for any encrypted value.

Source

pub fn collect_all_vars_from_dir( &self, dir: impl AsRef<Path>, ) -> SecretsResult<(HashMap<String, String>, Vec<PathBuf>)>

Collects all environment variables from .env files using dynamic dimension discovery, without modifying the process environment.

This method implements the same dynamic dimension discovery as load_from_dir, but returns the collected variables instead of setting them in the environment.

Use this method when you need the merged variables for inspection (e.g., list or dump commands) without side effects.

§Returns

A tuple of (variables, loaded_paths) where:

  • variables is a HashMap of all decrypted key-value pairs
  • loaded_paths is the list of file paths that were loaded, in order
§Errors

Returns an error if any file cannot be read or parsed, or if decryption fails for any encrypted value.

Source

pub fn resolve_env_paths(&self, dir: &Path) -> Vec<PathBuf>

Computes the ordered list of env file paths to load.

This method uses a power-set generation approach: it resolves ENV, OS, ARCH, USER, and VARIANT from the environment, then generates all possible combinations of these values (maintaining canonical order: ENV, OS, ARCH, USER, VARIANT).

Files are loaded in specificity order - more parts means more specific, which means higher precedence.

§Returns

A vector of paths in load order (later paths override earlier ones).

§Examples

With ENV=local, OS=linux, ARCH=amd64, USER=alice, VARIANT=docker, this generates all combinations like:

  • .env
  • .env.local
  • .env.linux
  • .env.amd64
  • .env.alice
  • .env.docker
  • .env.local.linux
  • .env.local.docker
  • … (all 31 non-empty subsets)
  • .env.local.linux.amd64.alice.docker
  • .env.pr-<NUMBER> (if applicable)
Source

pub fn load_env_file( &self, path: &Path, ) -> SecretsResult<HashMap<String, String>>

Loads and decrypts a single .env file, returning key/value pairs.

§Errors

Returns an error if the file cannot be read or if decryption fails.

Source

pub fn parse_and_decrypt( &self, content: &str, path: &Path, ) -> SecretsResult<HashMap<String, String>>

Parses env file content and decrypts encrypted values.

§Errors

Returns an error if the content cannot be parsed or if decryption fails.

Source

pub fn get_var(&self, key: &str) -> SecretsResult<String>

Gets a decrypted environment variable value from the process environment.

If the value is encrypted, it will be automatically decrypted.

§Errors

Returns an error if the variable is not set or if decryption fails.

Source

pub fn get_var_or(&self, key: &str, default: &str) -> String

Gets a decrypted environment variable, or returns a default if not set.

§Examples
use dotenvage::EnvLoader;

let loader = EnvLoader::new()?;
let port = loader.get_var_or("PORT", "8080");
Source

pub fn set_var(&self, key: &str, value: &str) -> SecretsResult<PathBuf>

Sets a variable in .env.local in the current directory.

Values are automatically encrypted when AutoDetectPatterns::should_encrypt returns true, except AGE key configuration variables (e.g., AGE_KEY_NAME) which are always stored as plaintext.

§Errors

Returns an error if:

  • The target file cannot be read or parsed
  • Encryption fails
  • The updated file cannot be written
Source

pub fn set_var_in_dir( &self, key: &str, value: &str, dir: impl AsRef<Path>, ) -> SecretsResult<PathBuf>

Sets a variable in .env.local in a specific directory.

Returns the path that was written ({dir}/.env.local).

§Errors

Returns an error if:

  • The target file cannot be read or parsed
  • Encryption fails
  • The updated file cannot be written
Source

pub fn set_var_in_file( &self, key: &str, value: &str, path: impl AsRef<Path>, ) -> SecretsResult<()>

Sets a variable in a specific .env file path.

Existing key-value pairs are preserved (with the target key updated), and output is written in deterministic sorted order.

Values are automatically encrypted when AutoDetectPatterns::should_encrypt returns true, except AGE key configuration variables (e.g., AGE_KEY_NAME) which are always stored as plaintext.

§Errors

Returns an error if:

  • The target file cannot be read or parsed
  • Encryption fails
  • The updated file cannot be written
Source

pub fn get_all_variable_names(&self) -> SecretsResult<Vec<String>>

Gets all variable names from all .env* files that would be loaded.

This method uses the standard file-loading algorithm (via resolve_env_paths) to determine which files would be loaded, then collects all unique variable names across those files.

Files are processed in the same order as load(), but this method only collects the variable names without loading them into the environment.

§Returns

A vector of unique variable names found across all .env* files that would be loaded. If a variable appears in multiple files, it only appears once in the result.

Note: AGE key variables (DOTENVAGE_AGE_KEY, AGE_KEY, EKG_AGE_KEY, AGE_KEY_NAME, and any variable ending with _AGE_KEY_NAME) are filtered out for security reasons.

§Errors

Returns an error if any file cannot be read or parsed. Unlike load(), this method fails fast on the first error encountered.

§Examples
use dotenvage::EnvLoader;

let loader = EnvLoader::new()?;
let variable_names = loader.get_all_variable_names()?;
println!("Found {} variables", variable_names.len());
for name in &variable_names {
    println!("  - {}", name);
}
Source

pub fn get_all_variable_names_from_dir( &self, dir: impl AsRef<Path>, ) -> SecretsResult<Vec<String>>

Gets all variable names from all .env* files in a specific directory.

Like get_all_variable_names(), but loads from a specific directory instead of the current directory.

Note: AGE key variables are filtered out for security reasons.

§Errors

Returns an error if any file cannot be read or parsed.

§Examples
use dotenvage::EnvLoader;

let loader = EnvLoader::new()?;
let variable_names = loader.get_all_variable_names_from_dir("./config")?;
Source

pub fn dump_to_writer<W: Write>(&self, writer: W) -> SecretsResult<()>

Write all merged .env variables in KEY=VALUE format to a writer.

Automatically filters out AGE key variables (DOTENVAGE_AGE_KEY, AGE_KEY, EKG_AGE_KEY, AGE_KEY_NAME, and any variable ending with _AGE_KEY_NAME).

This function:

  1. Loads all .env* files in standard order
  2. Merges variables (later files override earlier ones)
  3. Decrypts encrypted values
  4. Filters out AGE key variables
  5. Writes in simple KEY=VALUE format (quotes added when needed)
§Errors

Returns an error if:

  • Writing to the writer fails
  • .env files cannot be loaded
  • Decryption fails for any encrypted value
§Examples
use std::io::Cursor;

use dotenvage::EnvLoader;

let loader = EnvLoader::new()?;
let mut buffer = Vec::new();
loader.dump_to_writer(&mut buffer)?;
let output = String::from_utf8(buffer)?;
println!("{}", output);
Source

pub fn dump_to_writer_from_dir<W: Write>( &self, dir: impl AsRef<Path>, writer: W, ) -> SecretsResult<()>

Write all merged .env variables in KEY=VALUE format to a writer from a specific directory.

Same as dump_to_writer but loads from a specific directory.

§Errors

Returns an error if:

  • Writing to the writer fails
  • .env files cannot be loaded
  • Decryption fails for any encrypted value
Source

pub fn resolve_env() -> String

Resolves the <ENV> placeholder for environment-specific file names.

The environment name is resolved in the following order (higher numbers take precedence):

  1. DOTENVAGE_ENV environment variable (preferred)
  2. EKG_ENV environment variable (alternative)
  3. VERCEL_ENV environment variable
  4. NODE_ENV environment variable
  5. .env file (as fallback - checks for DOTENVAGE_ENV, EKG_ENV, VERCEL_ENV, or NODE_ENV - plaintext only)
  6. Defaults to "local" if none of the above are set

Note: Environment variables always take precedence over values in .env files. The .env file is only checked if no environment variables are set.

The value is always converted to lowercase.

§Examples
use dotenvage::EnvLoader;

// With DOTENVAGE_ENV=production, returns "production"
// Without any env vars set, returns "local"
let env = EnvLoader::resolve_env();
println!("Environment: {}", env);
Source

pub fn resolve_arch() -> Option<String>

Resolves the <ARCH> placeholder for architecture-specific file names.

The architecture name is resolved from the first available source:

  1. DOTENVAGE_ARCH environment variable (preferred)
  2. EKG_ARCH environment variable (alternative)
  3. CARGO_CFG_TARGET_ARCH environment variable (Cargo build-time, e.g., “x86_64”, “aarch64”)
  4. TARGET environment variable (parsed for arch from target triple, e.g., “x86_64-unknown-linux-gnu” → “x86_64”)
  5. TARGETARCH environment variable (Docker multi-platform builds, e.g., “amd64”, “arm64”)
  6. TARGETPLATFORM environment variable (Docker, parsed for arch, e.g., “linux/arm64” → “arm64”)
  7. RUNNER_ARCH environment variable (GitHub Actions, e.g., “X64”, “ARM64”)
  8. Returns None if none are set
§Supported Architectures

The following architectures are recognized and normalized to canonical names:

  • amd64: AMD64/x86-64 (aliases: x64, x86_64, x86-64)
  • arm64: ARM 64-bit (aliases: aarch64)
  • arm: ARM 32-bit (aliases: armv7, armv7l, armhf)
  • i386: x86 32-bit (aliases: i686, x86)
  • riscv64: RISC-V 64-bit (aliases: riscv64gc)
  • ppc64le: PowerPC 64-bit LE (aliases: powerpc64le)
  • s390x: IBM System/390

Unknown values are passed through as-is (lowercase) for custom use cases.

§Examples
use dotenvage::EnvLoader;

// With TARGETARCH=arm64 (Docker build), resolves to Some("arm64")
// With RUNNER_ARCH=X64 (GitHub Actions), resolves to Some("amd64")
if let Some(arch) = EnvLoader::resolve_arch() {
    println!("Architecture: {}", arch);
}
Source

pub fn resolve_os() -> Option<String>

Resolves the <OS> placeholder for OS-specific file names.

The operating system is resolved from the first available source:

  1. DOTENVAGE_OS environment variable (preferred)
  2. EKG_OS environment variable (alternative)
  3. CARGO_CFG_TARGET_OS environment variable (Cargo build-time, e.g., “linux”, “macos”, “windows”)
  4. TARGET environment variable (parsed from target triple, e.g., “x86_64-unknown-linux-gnu” → “linux”)
  5. RUNNER_OS environment variable (GitHub Actions, e.g., “Linux”, “macOS”, “Windows”)
  6. std::env::consts::OS (runtime detection)
§Supported Operating Systems
  • linux: Linux
  • macos: macOS (aliases: darwin, osx)
  • windows: Windows (aliases: win32, win)
  • freebsd: FreeBSD
  • openbsd: OpenBSD
  • netbsd: NetBSD
  • android: Android
  • ios: iOS
§Examples
use dotenvage::EnvLoader;

// Typically auto-detects from runtime or build-time
if let Some(os) = EnvLoader::resolve_os() {
    println!("OS: {}", os);
}
Source

pub fn resolve_user() -> Option<String>

Resolves the <USER> placeholder for user-specific file names.

The username is resolved from the first available environment variable:

  1. DOTENVAGE_USER (preferred)
  2. EKG_USER
  3. GITHUB_ACTOR (GitHub Actions)
  4. GITHUB_TRIGGERING_ACTOR (GitHub Actions)
  5. GITHUB_REPOSITORY_OWNER (GitHub Actions)
  6. USER (Unix standard)
  7. USERNAME (Windows standard)
  8. Returns None if none are set

The value is always converted to lowercase.

§Examples
use dotenvage::EnvLoader;

// Typically resolves from $USER on Unix or %USERNAME% on Windows
if let Some(user) = EnvLoader::resolve_user() {
    println!("User: {}", user);
}
Source

pub fn resolve_variant() -> Option<String>

Resolves the <VARIANT> placeholder for variant-specific file names.

The variant name is resolved from the first available environment variable:

  1. DOTENVAGE_VARIANT (preferred)
  2. EKG_VARIANT
  3. VARIANT
  4. Returns None if none are set

The value is always converted to lowercase.

This dimension is useful for deployment variants like:

  • docker - Docker containerized deployment
  • kubernetes or k8s - Kubernetes deployment
  • lambda - AWS Lambda deployment
  • canary - Canary release
  • blue / green - Blue-green deployment variants
§Examples
use dotenvage::EnvLoader;

// With VARIANT=docker, returns Some("docker")
// Without any variant set, returns None
if let Some(variant) = EnvLoader::resolve_variant() {
    println!("Variant: {}", variant);
}
Source

pub fn resolve_pr_number() -> Option<String>

Resolves the <PR_NUMBER> placeholder for PR-specific file names.

The PR number is only resolved in GitHub Actions pull request contexts:

  1. Checks that GITHUB_EVENT_NAME starts with "pull_request"
  2. Reads from PR_NUMBER environment variable
  3. Falls back to parsing GITHUB_REF (e.g., refs/pull/123/merge)
  4. Returns None if not in a PR context
§Examples
use dotenvage::EnvLoader;

// In GitHub Actions PR, resolves to Some("123")
// Outside of PR context, returns None
if let Some(pr_number) = EnvLoader::resolve_pr_number() {
    println!("PR Number: {}", pr_number);
}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V