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:
| Priority | Source | Example |
|---|---|---|
| 1 | System environment | Host's environment (if env_inherit_mode allows) |
| 2 | Root files | root.env.files |
| 3 | Command files | command.env.files |
| 4 | Implementation files | implementations[].env.files |
| 5 | Root vars | root.env.vars |
| 6 | Command vars | command.env.vars |
| 7 | Implementation vars | implementations[].env.vars |
| 8 | Invowk vars | INVOWK_*, ARG* |
| 9 | CLI env files | --ivk-env-file .env.local |
| 10 | CLI 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
-
Start with system environment (e.g.,
PATH,HOME) ifenv_inherit_modeis notnone -
Load root files (
.env):API_URL=http://envfile.example.comDATABASE_URL=postgres://localhost/db
-
Load command files (
.env.build):BUILD_MODE=releaseCACHE_DIR=./cache
-
Load implementation files (
implementations[].env.files):- (none in this example)
-
Apply root vars (override files):
API_URL=http://root.example.com← overrides.envLOG_LEVEL=info
-
Apply command vars (override files):
API_URL=http://command.example.com← overrides rootBUILD_MODE=development← overrides.env.build
-
Apply implementation vars:
BUILD_MODE=production← overrides commandNODE_ENV=production
-
Apply Invowk vars (flags/args):
INVOWK_FLAG_*,INVOWK_ARG_*(if present)
-
Apply CLI overrides (if provided):
--ivk-env-filethen--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"}]
}]
}