Skip to main content
Version: 0.5.0

Environment Precedence

When the same variable is defined in multiple places, Invowk™ follows a specific precedence order. Higher precedence sources override lower ones.

Precedence Order

Applied from lowest to highest priority:

PrioritySourceExample
1System environmentHost's environment (if env_inherit_mode allows)
2Root filesroot.env.files
3Command filescommand.env.files
4Implementation filesimplementations[].env.files
5Root varsroot.env.vars
6Command varscommand.env.vars
7Implementation varsimplementations[].env.vars
8Invowk varsINVOWK_*, ARG*
9CLI env files--ivk-env-file .env.local
10CLI env vars--ivk-env-var KEY=value

Visual Hierarchy

System Environment (lowest priority)

Files (by scope, lowest to highest)
├── Root env.files
├── Command env.files
└── Implementation env.files

Vars (by scope, lowest to highest)
├── Root env.vars
├── Command env.vars
└── Implementation env.vars

Invowk Vars
├── INVOWK_*
└── ARG*

CLI (highest priority)
├── --ivk-env-file .env.local
└── --ivk-env-var KEY=value

Example Walkthrough

Given this invowkfile:

// Root level
env: {
files: [".env"]
vars: {
API_URL: "http://root.example.com"
LOG_LEVEL: "info"
}
}

cmds: [
{
name: "build"
// Command level
env: {
files: [".env.build"]
vars: {
API_URL: "http://command.example.com"
BUILD_MODE: "development"
}
}
implementations: [{
script: "echo $API_URL $LOG_LEVEL $BUILD_MODE $NODE_ENV"
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
// Implementation level
env: {
vars: {
BUILD_MODE: "production"
NODE_ENV: "production"
}
}
}]
}
]

And these files:

# .env
API_URL=http://envfile.example.com
DATABASE_URL=postgres://localhost/db

# .env.build
BUILD_MODE=release
CACHE_DIR=./cache

Resolution Order

  1. Start with system environment (e.g., PATH, HOME) if env_inherit_mode is not none

  2. Load root files (.env):

    • API_URL=http://envfile.example.com
    • DATABASE_URL=postgres://localhost/db
  3. Load command files (.env.build):

    • BUILD_MODE=release
    • CACHE_DIR=./cache
  4. Load implementation files (implementations[].env.files):

    • (none in this example)
  5. Apply root vars (override files):

    • API_URL=http://root.example.com ← overrides .env
    • LOG_LEVEL=info
  6. Apply command vars (override files):

    • API_URL=http://command.example.com ← overrides root
    • BUILD_MODE=development ← overrides .env.build
  7. Apply implementation vars:

    • BUILD_MODE=production ← overrides command
    • NODE_ENV=production
  8. Apply Invowk vars (flags/args):

    • INVOWK_FLAG_*, INVOWK_ARG_* (if present)
  9. Apply CLI overrides (if provided):

    • --ivk-env-file then --ivk-env-var (highest priority)

Final Result

API_URL=http://command.example.com    # From command vars
LOG_LEVEL=info # From root vars
BUILD_MODE=production # From implementation vars
NODE_ENV=production # From implementation vars
DATABASE_URL=postgres://localhost/db # From .env file
CACHE_DIR=./cache # From .env.build file

With CLI Override

invowk cmd build --ivk-env-var API_URL=http://cli.example.com

Now API_URL=http://cli.example.com because CLI has highest priority.

Vars Always Override Files

All env.files are loaded first (root → command → implementation), then all env.vars are applied on top (root → command → implementation). This means even root-level vars override implementation-level files:

env: {
files: [".env"] // API_URL=from-file
vars: {
API_URL: "from-vars" // This wins
}
}

Multiple Files at Same Level

Files are loaded in order; later files override earlier:

env: {
files: [
".env", // API_URL=base
".env.local", // API_URL=local (wins)
]
}

Platform-Specific Variables

For platform-specific environment variables, use separate implementations per platform:

// Platform-specific env requires separate implementations
implementations: [
{
script: "echo $CONFIG_PATH"
runtimes: [{name: "native"}]
platforms: [{name: "linux"}]
env: {
vars: {
CONFIG_PATH: "/etc/app"
OTHER_VAR: "value"
}
}
},
{
script: "echo $CONFIG_PATH"
runtimes: [{name: "native"}]
platforms: [{name: "macos"}]
env: {
vars: {
CONFIG_PATH: "/usr/local/etc/app"
OTHER_VAR: "value"
}
}
}
]

Each implementation has its own env that is applied when that platform is selected.

Best Practices

Use Appropriate Levels

// Root: shared across all commands
env: {
vars: {
PROJECT_NAME: "myapp"
VERSION: "1.0.0"
}
}

// Command: specific to this command
{
name: "build"
env: {
vars: {
BUILD_TARGET: "production"
}
}
}

// Implementation: specific to this runtime
implementations: [{
runtimes: [{name: "container", image: "node:20"}]
platforms: [{name: "linux"}]
env: {
vars: {
NODE_OPTIONS: "--max-old-space-size=4096"
}
}
}]

Override Pattern

Base config in files, overrides in vars:

env: {
files: [".env"] // Defaults
vars: {
OVERRIDE_THIS: "value" // Specific override
}
}

Local Development

Use optional local files for developer overrides:

env: {
files: [
".env", // Committed defaults
".env.local?", // Not committed, personal overrides
]
}

CLI for Temporary Overrides

# Quick test with different config
invowk cmd build --ivk-env-var DEBUG=true --ivk-env-var LOG_LEVEL=debug

Debugging Precedence

To see final values, add debug output:

{
name: "debug-env"
implementations: [{
script: """
echo "API_URL=$API_URL"
echo "LOG_LEVEL=$LOG_LEVEL"
echo "BUILD_MODE=$BUILD_MODE"
env | sort
"""
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}

Next Steps