Environment Files
Environment Files¶
Load environment variables from .env files:
# Environment files to load
env_files:
- .env
- .env.local
- config/.env.production
commands:
- shell: "echo $DATABASE_URL"
Environment File Format:
Environment files use the standard .env format with KEY=value pairs:
# .env file example
DATABASE_URL=postgresql://localhost:5432/mydb
REDIS_HOST=localhost
REDIS_PORT=6379
# Comments are supported (lines starting with #)
API_KEY=secret-key-here
# Empty lines are ignored
# Multi-line values use quotes
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBg...
-----END PRIVATE KEY-----"
Quote Handling:
Prodigy automatically strips both single and double quotes from the start and end of values during parsing:
# These all produce the same value: myvalue
KEY1=myvalue
KEY2="myvalue"
KEY3='myvalue'
# For multi-line values, quotes are required but will be stripped
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBg...
-----END PRIVATE KEY-----"
# Resulting value does NOT include the surrounding quotes
Source: src/cook/environment/manager.rs:200-207
Parsing Rules:
- Lines starting with
#are treated as comments and skipped - Empty lines are ignored
- Each line must contain an
=character to be valid - Key is everything before the first
=(trimmed) - Value is everything after the first
=(trimmed) - Surrounding quotes (
"or') are automatically removed from values
Source: src/cook/environment/manager.rs:190-211
Loading Order and Precedence:
Environment files are loaded in order (if they exist), with later files overriding earlier files. Missing files are silently skipped with debug logging. This enables layered configuration:
env_files:
- .env # Base configuration
- .env.local # Local overrides (gitignored)
- .env.production # Environment-specific settings
Example override behavior:
# .env (base)
DATABASE_URL=postgresql://localhost:5432/dev
API_TIMEOUT=30
# .env.production (overrides)
DATABASE_URL=postgresql://prod-server:5432/app
# API_TIMEOUT remains 30 from base file
Precedence order (highest to lowest):
1. Global env field in workflow YAML
2. Later files in env_files list
3. Earlier files in env_files list
4. Parent process environment
File Paths and Resolution:
Environment file paths can be:
- Absolute paths: /etc/myapp/.env
- Relative paths: Resolved relative to the workflow file location (e.g., .env, config/.env.production)
Source: src/cook/environment/manager.rs:182-215
Error Handling:
Prodigy handles environment files with the following behavior:
- Missing files: Silently skipped with debug logging (
"Environment file not found: {path}"). This allows optional configuration files and environment-specific files that may not exist in all contexts. - File read errors: Will halt workflow execution with an error (e.g., permission denied)
- Invalid syntax: Will halt workflow execution with an error (e.g., malformed
.envformat)
Source: src/cook/environment/manager.rs:184-186
Example with missing file:
env_files:
- .env # Always exists (base config)
- .env.local # May not exist (personal overrides)
- .env.${ENVIRONMENT} # May not exist (environment-specific)
In this example, if .env.local doesn't exist, Prodigy logs a debug message and continues. Only .env needs to exist. This is useful for:
- Personal configuration files that are gitignored
- Environment-specific files (.env.production, .env.staging)
- Optional feature flags or overrides
Troubleshooting:
To verify which env files are being loaded, run Prodigy with verbose logging:
# Enable debug logging to see which files are loaded/skipped
RUST_LOG=debug prodigy run workflow.yml
You'll see messages like:
DEBUG prodigy::cook::environment - Environment file not found: .env.local
INFO prodigy::cook::environment - Loaded environment from: .env
Common Syntax Errors:
These will cause workflow execution to halt:
# INVALID - No = character
INVALID_LINE
# VALID - Value can be empty
EMPTY_VALUE=
# INVALID - Unbalanced quotes (opening quote without closing)
BAD_QUOTE="unclosed value
# VALID - Quotes must match
GOOD_QUOTE_1="value with spaces"
GOOD_QUOTE_2='single quoted value'
# INVALID - Mixed quotes
MIXED_QUOTES="value'
# VALID - Embedded quotes of opposite type
EMBEDDED="value with 'single' quotes inside"
Integration with Profiles and Secrets¶
Environment files work seamlessly with other environment features like profiles and secrets management.
Combining env_files with profiles:
# Base configuration in .env file
env_files:
- .env
# Profile-specific overrides
profiles:
dev:
API_URL: http://localhost:3000
DEBUG: "true"
prod:
API_URL: https://api.production.com
DEBUG: "false"
commands:
- shell: "echo $API_URL" # Uses profile value if active, otherwise .env value
The precedence order is:
1. Profile-specific values (if profile active)
2. Global env field values
3. Environment file values (later files override earlier)
4. Parent process environment
Loading secrets from env_files:
Environment files can contain secrets, but you must explicitly mark them as secrets in the workflow configuration:
# .env.secrets file
API_KEY=sk-abc123xyz
DATABASE_PASSWORD=secret-password
# Workflow configuration
env_files:
- .env.secrets
secrets:
# Retrieve from environment variable (loaded from .env.secrets)
API_KEY: "${env:API_KEY}"
DATABASE_PASSWORD: "${env:DATABASE_PASSWORD}"
Note: Variables loaded from env_files are NOT automatically masked. You must explicitly mark them as secrets in the secrets section for masking in logs.
Complete integration example:
# Layered configuration strategy
env_files:
- .env # Base configuration
- .env.local # Local overrides (gitignored)
- .env.${ENVIRONMENT} # Environment-specific (e.g., .env.production)
env:
PROJECT_NAME: my-project
VERSION: "1.0.0"
secrets:
# Secrets loaded from env files, masked in logs
API_KEY: "${env:API_KEY}"
DATABASE_URL: "${env:DATABASE_URL}"
profiles:
dev:
MAX_WORKERS: "2"
TIMEOUT: "60"
prod:
MAX_WORKERS: "20"
TIMEOUT: "30"
commands:
- shell: "echo 'Project: $PROJECT_NAME v$VERSION'"
- shell: "echo 'Workers: $MAX_WORKERS, Timeout: $TIMEOUT'"
- shell: "curl -H 'Authorization: Bearer ***' $API_URL" # API_KEY masked
Best practices for organizing env files:
- .env: Base configuration, safe to commit (no secrets)
- .env.local: Personal overrides, add to .gitignore
- .env.production / .env.staging / .env.dev: Environment-specific, may contain encrypted secrets
- .env.secrets: Sensitive values, NEVER commit, always in .gitignore
Precedence example:
# .env (base)
API_URL=http://localhost:3000
MAX_WORKERS=5
TIMEOUT=30
# .env.production (overrides)
API_URL=https://api.production.com
MAX_WORKERS=20
env_files:
- .env
- .env.production # Overrides API_URL and MAX_WORKERS
env:
TIMEOUT: "60" # Overrides TIMEOUT from both files
profiles:
prod:
MAX_WORKERS: "50" # Overrides MAX_WORKERS when --profile prod used
Final values when running with --profile prod:
- API_URL: https://api.production.com (from .env.production)
- MAX_WORKERS: 50 (from prod profile - highest precedence)
- TIMEOUT: 60 (from global env field - overrides files)