Skip to main content
Version: 0.3.0

Env Files

Load environment variables from .env files. This is a common pattern for managing configuration, especially secrets that shouldn't be committed to version control.

Basic Usage

{
name: "build"
env: {
files: [".env"]
}
implementations: [...]
}

With a .env file:

# .env
API_KEY=secret123
DATABASE_URL=postgres://localhost/mydb
DEBUG=false

Variables are loaded and available in your script.

File Format

Standard .env format:

# Comments start with #
KEY=value

# Quoted values (spaces preserved)
MESSAGE="Hello World"
PATH_WITH_SPACES='/path/to/my file'

# Empty value
EMPTY_VAR=

# No value (same as empty)
NO_VALUE

# Multiline (use quotes)
MULTILINE="line1
line2
line3"

Optional Files

Suffix with ? to make a file optional:

env: {
files: [
".env", // Required - error if missing
".env.local?", // Optional - ignored if missing
".env.secrets?", // Optional
]
}

This is useful for:

  • Local overrides that may not exist
  • Environment-specific files
  • Developer-specific settings

File Order

Files are loaded in order; later files override earlier ones:

env: {
files: [
".env", // Base config
".env.${ENV}?", // Environment-specific overrides
".env.local?", // Local overrides (highest priority)
]
}

Example with ENV=production:

# .env
API_URL=http://localhost:3000
DEBUG=true

# .env.production
API_URL=https://api.example.com
DEBUG=false

# .env.local (developer override)
DEBUG=true

Result:

  • API_URL=https://api.example.com (from .env.production)
  • DEBUG=true (from .env.local)

Path Resolution

Paths are relative to the invowkfile location:

project/
├── invowkfile.cue
├── .env # files: [".env"]
├── config/
│ └── .env.prod # files: ["config/.env.prod"]
└── src/

For modules, paths are relative to the module root.

Variable Interpolation

Use ${VAR} to include other environment variables:

env: {
files: [
".env",
".env.${NODE_ENV}?", // Uses NODE_ENV value
".env.${USER}?", // Uses current user
]
}
# If NODE_ENV=production, loads:
# - .env
# - .env.production (if exists)
# - .env.john (if exists and USER=john)

Scope Levels

Env files can be loaded at multiple levels:

Root Level

env: {
files: [".env"] // Loaded for all commands
}

cmds: [...]

Command Level

{
name: "build"
env: {
files: [".env.build"] // Only for this command
}
implementations: [...]
}

Implementation Level

{
name: "build"
implementations: [
{
script: "npm run build"
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
env: {
files: [".env.node"] // Only for this implementation
}
}
]
}

Combined with Variables

Use both files and direct variables:

env: {
files: [".env"]
vars: {
// These override values from .env
OVERRIDE_VALUE: "from-invowkfile"
}
}

Variables in vars always override values from files. All files are loaded first (root → command → implementation), then all vars are applied on top. See Precedence for the full order.

CLI Override

Load additional files at runtime:

# Load extra file
invowk cmd build --ivk-env-file .env.custom

# Multiple files
invowk cmd build --ivk-env-file .env.custom --ivk-env-file .env.secrets

CLI files have highest priority and override all invowkfile-defined sources.

Real-World Examples

Development vs Production

{
name: "start"
env: {
files: [
".env", // Base config
".env.${NODE_ENV:-dev}?", // Environment-specific
".env.local?", // Local overrides
]
}
implementations: [{
script: "node server.js"
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

Secrets Management

{
name: "deploy"
env: {
files: [
".env", // Non-sensitive config
".env.secrets?", // Sensitive - not in git
]
}
implementations: [{
script: """
echo "Deploying with API_KEY..."
./deploy.sh
"""
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}

.gitignore:

.env.secrets
.env.local

Multi-Environment

project/
├── invowkfile.cue
├── .env # Shared defaults
├── .env.development # Dev settings
├── .env.staging # Staging settings
└── .env.production # Production settings
{
name: "deploy"
env: {
files: [
".env",
".env.${DEPLOY_ENV}", // DEPLOY_ENV must be set
]
}
depends_on: {
env_vars: [
{alternatives: [{name: "DEPLOY_ENV", validation: "^(development|staging|production)$"}]}
]
}
implementations: [...]
}

Best Practices

  1. Use .env for defaults: Base configuration that works for everyone
  2. Use .env.local for overrides: Developer-specific settings, not in git
  3. Use .env.{environment} for environments: Production, staging, etc.
  4. Mark sensitive files optional: They may not exist in all environments
  5. Don't commit secrets: Add .env.secrets, .env.local to .gitignore

Next Steps