Skip to content

Environment Variables

Environment variables configure the runtime environment for your workflows. Boltbase supports defining variables at three levels: base configuration, DAG-level, and step-level.

Overview

Variables flow from base configuration through DAG definition to individual steps:

Base Config (shared) → DAG-level (workflow-specific) → Step-level (step-specific)

Each level can reference and build upon variables from previous levels. Step-level variables override DAG-level variables with the same name.

yaml
# Example showing all three levels
env:
  - APP_ENV: production      # DAG-level
  - LOG_DIR: ${HOME}/logs    # Reference system variable

steps:
  - name: deploy
    env:
      - APP_ENV: staging     # Overrides DAG-level for this step only
    command: ./deploy.sh

Base Configuration Inheritance

Define shared environment variables in ~/.config/boltbase/base.yaml (or set BOLTBASE_BASE_CONFIG to a custom path). All DAGs inherit these variables.

yaml
# ~/.config/boltbase/base.yaml
env:
  - ENVIRONMENT: production
  - API_ENDPOINT: https://api.example.com
  - NOTIFY_EMAIL: ops@example.com

Merging Behavior

DAG-level variables are appended to base configuration variables, not replaced:

yaml
# base.yaml
env:
  - SHARED_VAR: base_value
  - ENV: production

# my-dag.yaml
env:
  - DAG_VAR: dag_value
  - ENV: staging           # Overrides base ENV

# Result at runtime:
# SHARED_VAR=base_value (from base)
# ENV=staging (DAG overrides base)
# DAG_VAR=dag_value (from DAG)

Inherited Fields

The following fields are inherited from base configuration:

FieldDescription
envEnvironment variables (appended)
paramsDefault parameters
log_dirLog directory
hist_retention_daysHistory retention
handler_onLifecycle handlers
smtpEmail configuration

DAG-Level Variables

Define variables accessible to all steps in a workflow:

yaml
env:
  - DATA_DIR: /var/data
  - OUTPUT_DIR: ${DATA_DIR}/output
  - TIMESTAMP: "`date +%Y%m%d_%H%M%S`"

steps:
  - command: python process.py --output ${OUTPUT_DIR}

Supported Formats

Boltbase supports multiple formats for defining environment variables:

yaml
# Format 1: Array of Maps (preserves order)
env:
  - KEY1: value1
  - KEY2: value2
  - KEY3: ${KEY1}_suffix  # Can reference earlier vars

# Format 2: Simple Map (order not guaranteed)
env:
  KEY1: value1
  KEY2: value2

# Format 3: Array of KEY=value strings
env:
  - KEY1=value1
  - KEY2=value2

# Format 4: Mixed format
env:
  - KEY1: value1
  - KEY2=value2
  - KEY3: ${KEY1}

Note: The array format (Format 1) preserves order, which matters when later variables reference earlier ones. The simple map format (Format 2) does not guarantee order.

Non-String Values

Non-string values (integers, booleans, floats) are automatically converted to strings:

yaml
env:
  - PORT: 8080           # Becomes "8080"
  - ENABLED: true        # Becomes "true"
  - RATIO: 0.75          # Becomes "0.75"

Variable Expansion

Reference other variables using ${VAR} or $VAR syntax. Earlier variables in the list can be referenced by later ones:

yaml
env:
  - BASE_PATH: /opt/app
  - BIN_DIR: ${BASE_PATH}/bin      # References BASE_PATH
  - CONFIG_FILE: ${BASE_PATH}/config.yaml

Command Substitution

Execute commands at DAG load time using backticks:

yaml
env:
  - TODAY: "`date +%Y-%m-%d`"
  - GIT_COMMIT: "`git rev-parse --short HEAD`"
  - HOSTNAME: "`hostname -f`"

Referencing System Variables

For security, Boltbase filters which system environment variables are available. To use system variables in your workflow, explicitly reference them:

yaml
env:
  - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
  - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
  - DATABASE_URL: ${DATABASE_URL}

See Security Considerations for details on variable filtering.

Step-Level Variables

Define variables specific to individual steps. These override DAG-level variables with the same name:

yaml
env:
  - LOG_LEVEL: info

steps:
  - name: normal-processing
    command: ./process.sh
    # Uses LOG_LEVEL=info from DAG-level

  - name: debug-processing
    env:
      - LOG_LEVEL: debug    # Overrides for this step only
    command: ./process.sh

  - name: final-step
    command: ./cleanup.sh
    # Uses LOG_LEVEL=info again (step-level doesn't persist)

Step-level variables support the same features as DAG-level:

yaml
steps:
  - name: process-data
    env:
      - INPUT_PATH: ${DATA_DIR}/input
      - TIMESTAMP: "`date +%Y%m%d_%H%M%S`"
      - WORKER_ID: worker_${HOSTNAME}
    command: python process.py

Variable Expansion Syntax

Basic Syntax

These patterns work in all contexts:

PatternDescriptionExample
${VAR}Braced substitution${HOME}/home/user
$VARSimple substitution$HOME/home/user
'$VAR'Single-quoted (no expansion)'$VAR''$VAR'
\$Literal dollar (non-shell only)\$9.99$9.99

Notes:

  • \$ is only unescaped when Boltbase is the final evaluator (non-shell executors and config fields).
  • Shell-executed commands keep native shell semantics. Use shell escaping there.
  • To get a literal $$ in non-shell contexts, escape both dollars: \$\$.

Unknown Variable Handling

What happens when a variable is not defined depends on the execution context:

ContextBehaviorExample
Local shell execution (default)Unknown vars become empty$UNDEFINED → ``
Non-shell executors (docker, http, ssh, jq, mail, etc.)OS-only vars preserved as-is$HOME$HOME

For non-shell executors, OS-only variables not defined in the DAG scope pass through unchanged to the target environment (container, remote shell, etc.), which resolves them. DAG-scoped variables (env, params, secrets, step outputs) are still expanded normally.

Shell Expansion Syntax (Local Execution Only)

When executing commands locally with the default shell executor, Boltbase uses POSIX shell expansion via mvdan.cc/sh. These patterns work only in that context:

PatternDescription
${VAR:-default}Use default if VAR is unset or empty
${VAR:=default}Set VAR to default if unset or empty
${VAR:?message}Error with message if VAR is unset or empty
${VAR:+alternate}Use alternate if VAR is set and non-empty
${VAR:offset:length}Substring extraction

These patterns do not work for non-shell executors (docker, http, ssh, jq, mail, etc.). In those cases, only basic $VAR and ${VAR} syntax is supported, and OS-only variables pass through unchanged to the target environment.

Escaped Backticks

To use literal backticks without command substitution:

yaml
command: echo "Literal backtick: \`not a command\`"

For JSON path access and step output references, see Variables Reference.

Precedence Summary

When the same variable is defined at multiple levels, the highest-precedence value wins:

LevelPrecedenceDescription
Step env:HighestStep-specific variables
Output variablesFrom previous steps (output: field)
SecretsFrom secrets: block
DAG env: + dotenvWorkflow-level variables
ParametersFrom params: and CLI overrides
Base config env:Shared configuration
System environmentLowestFiltered OS variables

For detailed precedence rules, see Variables Reference - Precedence.

Security Considerations

System Environment Filtering

Boltbase filters which system environment variables are passed to step processes for security.

Automatically passed:

  • PATH, HOME, LANG, TZ, SHELL
  • Variables with prefixes: BOLTBASE_*, LC_*, DAG_*

Filtered out:

  • All other system variables (e.g., AWS_SECRET_ACCESS_KEY, DATABASE_URL)

To use sensitive system variables, explicitly reference them in your env: section:

yaml
env:
  - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
  - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}

Sensitive Values

For truly sensitive values, use the Secrets feature instead of env::

yaml
secrets:
  - name: API_TOKEN
    provider: env
    key: PROD_API_TOKEN

steps:
  - command: ./deploy.sh
    # API_TOKEN is available but masked in logs

See Also

Released under the MIT License.