Skip to main content
Version: 0.1.0-alpha.3

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

From highest to lowest priority:

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

Visual Hierarchy

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

Invowk Vars
├── INVOWK_FLAG_*
└── INVOWK_ARG_*

Implementation Level
├── env.vars
└── env.files

Command Level
├── env.vars
└── env.files

Root Level
├── env.vars
└── env.files

System Environment (lowest priority)

Example Walkthrough

Given this invkfile:

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

    • --invk-env-file then --invk-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 --invk-env-var API_URL=http://cli.example.com

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

Files vs Vars at Same Level

Within the same level, vars override 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 --invk-env-var DEBUG=true --invk-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