DOC_INDEX
THEME
DOCS/Example Cookbook/Environment Management Examples

Environment Management Examples

OrchStep examples for environment variables, scoping, and file loading


env between steps

Environment Variable Scoping Between Steps

# Example: Environment Variable Scoping Between Steps
# Shows how env vars flow between steps and tasks, including override
# precedence and scope isolation.
#
# Precedence (highest to lowest):
#   Step-level env  >  Task-level env  >  Workflow-level env
#
# When a step calls a task with `env:`, those vars override the
# target task's own `env:` for that invocation only.
#
# Try: orchstep run
# Try: orchstep run show_override_hierarchy

name: env-between-steps
desc: "Environment variable scoping between steps and tasks"

tasks:
  # --- Step-level env overrides task-level env ---
  show_override_hierarchy:
    desc: "Step env overrides task env; function env overrides step env"
    env:
      DB_HOST: "task-db.example.com"
      DB_PORT: "5432"
      LOG_LEVEL: "info"
    steps:
      - name: task_level_only
        desc: "This step inherits task-level env"
        func: shell
        do: |
          echo "=== Task-level env ==="
          echo "DB_HOST   = {{ env.DB_HOST }}"
          echo "DB_PORT   = {{ env.DB_PORT }}"
          echo "LOG_LEVEL = {{ env.LOG_LEVEL }}"

      - name: step_override
        desc: "This step overrides DB_HOST for just this step"
        func: shell
        env:
          DB_HOST: "step-db.internal"
          LOG_LEVEL: "debug"
        do: |
          echo "=== Step-level override ==="
          echo "DB_HOST   = {{ env.DB_HOST }}"
          echo "DB_PORT   = {{ env.DB_PORT }}"
          echo "LOG_LEVEL = {{ env.LOG_LEVEL }}"

      - name: back_to_task_level
        desc: "After the step, task-level env is restored"
        func: shell
        do: |
          echo "=== Back to task-level ==="
          echo "DB_HOST   = {{ env.DB_HOST }}"
          echo "LOG_LEVEL = {{ env.LOG_LEVEL }}"

  # --- Passing env vars from a step to a called task ---
  backend_task:
    desc: "Backend service configuration"
    env:
      SERVICE_HOST: "default-backend.local"
      SERVICE_PORT: "8080"
    steps:
      - name: show_config
        func: shell
        do: |
          echo "SERVICE_HOST = {{ env.SERVICE_HOST }}"
          echo "SERVICE_PORT = {{ env.SERVICE_PORT }}"
          echo "EXTRA_VAR    = {{ env.EXTRA_VAR }}"

  call_task_with_env:
    desc: "Override target task's env from the calling step"
    steps:
      # Call backend_task with default env
      - name: call_with_defaults
        desc: "Use the task's own env"
        task: backend_task

      # Override env for this specific invocation
      - name: call_with_overrides
        desc: "Override SERVICE_HOST and inject EXTRA_VAR"
        task: backend_task
        env:
          SERVICE_HOST: "prod-backend.example.com"
          EXTRA_VAR: "injected-from-caller"

      # After the call, the override is gone (scope isolation)
      - name: call_again_with_defaults
        desc: "Overrides do not persist -- back to defaults"
        task: backend_task

  # --- Template expressions in env vars ---
  template_env:
    desc: "Env vars can use template expressions for dynamic values"
    steps:
      - name: set_base
        desc: "Set a base URL via env_var"
        func: env_var
        do: set
        args:
          vars:
            API_HOST: "api.example.com"
            API_VERSION: "v2"

      - name: use_template
        desc: "Build a connection string from env templates"
        func: shell
        env:
          API_URL: "https://{{ env.API_HOST }}/{{ env.API_VERSION }}"
        do: |
          echo "API_URL = {{ env.API_URL }}"

  main:
    desc: "Run all env scoping demos"
    steps:
      - name: demo_hierarchy
        task: show_override_hierarchy

      - name: demo_task_calls
        task: call_task_with_env

      - name: demo_templates
        task: template_env

      - name: summary
        func: shell
        do: |
          echo "=== Environment Scoping Rules ==="
          echo "  1. Step env overrides task env (for that step only)"
          echo "  2. Step->task env overrides target task env (for that call)"
          echo "  3. Overrides are scoped -- they do not persist"
          echo "  4. Env values support template expressions"

env file loading

Loading from .env and .envrc Files

# Example: Loading from .env and .envrc Files
# OrchStep can load environment variables from external files.
# Supports standard .env format and bash-style .envrc (with export).
#
# Features:
#   - Load one or multiple files in sequence (later files override earlier)
#   - Supports .env key=value format
#   - Supports .envrc with `export KEY=VALUE` format
#   - Loaded vars persist for the rest of the task
#
# You can also use hierarchical file-based configuration with env_config
# pointing to a directory of environment YAML files.
#
# Try: orchstep run
# Try: orchstep run show_hierarchical --env dev

name: env-file-loading
desc: "Loading environment variables from .env and .envrc files"

tasks:
  # --- Load from .env files ---
  load_env_files:
    desc: "Load env vars from external files"
    steps:
      # Load one or more .env / .envrc files
      # Files are loaded in order; later files override earlier ones
      - name: load_from_files
        desc: "Load vars from .envrc then override with prod.env"
        func: env_var
        do: load_env_file
        args:
          file:
            - ./config/.envrc       # Base config (export KEY=VALUE)
            - ./config/prod.env     # Prod overrides (KEY=VALUE)

      - name: show_loaded_vars
        desc: "Vars from both files are now available"
        func: shell
        do: |
          echo "HOST = $HOST"
          echo "PORT = $PORT"
          echo "DEBUG = $DEBUG"

  # --- Hierarchical file-based environments ---
  # Point env_config.env_dir to a directory of YAML files.
  # OrchStep loads: defaults.yml -> group.yml -> environment.yml
  show_hierarchical:
    desc: "Hierarchical environments loaded from a directory"
    steps:
      - name: show_resolved
        func: shell
        do: |
          echo "=== Hierarchical Environment Config ==="
          echo "app_name  = {{ vars.app_name }}"
          echo "db_host   = {{ vars.db_host }}"
          echo "replicas  = {{ vars.replicas }}"
          echo "log_level = {{ vars.log_level }}"

  main:
    desc: "Demonstrate env file loading"
    steps:
      - name: explain
        func: shell
        do: |
          echo "=== .env / .envrc File Loading ==="
          echo ""
          echo "Supported file formats:"
          echo ""
          echo "  .env format:"
          echo "    HOST=prod.example.com"
          echo "    PORT=6432"
          echo "    DATABASE_URL=postgres://host:port/app"
          echo ""
          echo "  .envrc format (bash-style):"
          echo "    export HOST=dev.example.com"
          echo "    export PORT=5432"
          echo "    export FEATURE_FLAGS='{\"beta\":true}'"
          echo ""
          echo "Load files with:"
          echo "  func: env_var"
          echo "  do: load_env_file"
          echo "  args:"
          echo "    file:"
          echo "      - ./base.envrc"
          echo "      - ./overrides.env"
          echo ""
          echo "Or use hierarchical YAML directories with:"
          echo "  env_config:"
          echo "    env_dir: environments"

env groups

Environment Groups

# Example: Environment Groups
# Manage multi-environment configuration (dev, staging, production)
# with shared group defaults and per-environment overrides.
#
# Structure:
#   env_groups  -- shared settings for groups of environments
#   environments -- per-environment settings that inherit from a group
#   defaults    -- base fallback values
#
# Precedence: Step > Task > Environment > Group > Defaults
#
# Try: orchstep run --env dev
# Try: orchstep run --env staging
# Try: orchstep run --env production
# Try: orchstep run show_precedence --env qa

name: env-groups-demo
desc: "Environment groups for multi-environment configuration"

# Base defaults -- used when no environment is selected
defaults:
  app_name: "payments-api"
  base_domain: "example.com"
  log_level: "info"
  replicas: 1
  db_host: ""
  api_host: ""
  monitoring_enabled: false

# Shared settings for groups of environments
env_groups:
  nonprod:
    vars:
      replicas: 2
      log_level: "debug"
      api_host: "api-nonprod"

  prod:
    vars:
      replicas: 10
      log_level: "error"
      api_host: "api"
      monitoring_enabled: true

# Individual environments inherit from their group
environments:
  dev:
    group: nonprod
    vars:
      db_host: "dev-db"

  qa:
    group: nonprod
    vars:
      db_host: "qa-db"
      replicas: 3          # Override the nonprod group default

  staging:
    group: nonprod
    vars:
      db_host: "staging-db"
      replicas: 5

  production:
    group: prod
    vars:
      db_host: "prod-db"

tasks:
  main:
    desc: "Display resolved configuration for the selected environment"
    steps:
      - name: show_config
        func: shell
        do: |
          echo "=== Resolved Configuration ==="
          echo "app_name           = {{ vars.app_name }}"
          echo "db_host            = {{ vars.db_host }}.{{ vars.base_domain }}"
          echo "api_url            = https://{{ vars.api_host }}.{{ vars.base_domain }}"
          echo "replicas           = {{ vars.replicas }}"
          echo "log_level          = {{ vars.log_level }}"
          echo "monitoring_enabled = {{ vars.monitoring_enabled }}"

  # Show that task/step vars still override environment vars
  show_precedence:
    desc: "Task vars override environment-level vars"
    vars:
      replicas: 100      # Task-level override
    steps:
      - name: task_level
        func: shell
        do: |
          echo "Task-level replicas = {{ vars.replicas }}"

      - name: step_level
        vars:
          replicas: 200  # Step-level override (highest in-file priority)
        func: shell
        do: |
          echo "Step-level replicas = {{ vars.replicas }}"

      - name: explain
        func: shell
        do: |
          echo ""
          echo "=== Variable Precedence ==="
          echo "  Step vars (200) > Task vars (100) > Environment > Group > Defaults"

env var basics

Environment Variable Basics

# Example: Environment Variable Basics
# How to set, save, load, and use environment variables in OrchStep.
#
# OrchStep provides the `env_var` function to manage environment
# variables programmatically. You can also set env vars directly
# on tasks and steps using the `env:` key.
#
# Access any env var in templates with {{ env.VAR_NAME }}.
#
# Try: orchstep run
# Try: orchstep run use_env_var_function

name: env-var-basics
desc: "Setting, loading, and using environment variables"

# Security policy: control which env vars are visible in logs
config:
  env_security:
    safe_vars:
      - APP_NAME
      - APP_PORT
      - LOG_LEVEL
    safe_patterns:
      - "APP_*"
    sensitive_patterns:
      - "SECRET_*"
      - "*_TOKEN"

tasks:
  # --- Set env vars directly on a task ---
  show_task_env:
    desc: "Env vars set at the task level"
    env:
      APP_NAME: "my-service"
      APP_PORT: "8080"
      LOG_LEVEL: "info"
    steps:
      - name: print_env
        desc: "Read env vars with {{ env.VAR_NAME }}"
        func: shell
        do: |
          echo "APP_NAME  = {{ env.APP_NAME }}"
          echo "APP_PORT  = {{ env.APP_PORT }}"
          echo "LOG_LEVEL = {{ env.LOG_LEVEL }}"

  # --- Use the env_var function for programmatic management ---
  use_env_var_function:
    desc: "Set, save, clear, and restore env vars with env_var function"
    env:
      APP_NAME: "api-gateway"
      APP_PORT: "3000"
    steps:
      # Save current environment to a named snapshot
      - name: save_snapshot
        desc: "Save env vars to a named set for later restoration"
        func: env_var
        do: save
        args:
          name: "before_deploy"

      # Programmatically set new env vars
      - name: set_deploy_vars
        desc: "Set additional env vars via the env_var function"
        func: env_var
        do: set
        args:
          vars:
            DEPLOY_TARGET: "production"
            DEPLOY_REGION: "us-west-2"

      - name: show_deploy_vars
        desc: "Verify new env vars are available"
        func: shell
        do: |
          echo "DEPLOY_TARGET = {{ env.DEPLOY_TARGET }}"
          echo "DEPLOY_REGION = {{ env.DEPLOY_REGION }}"
          echo "APP_NAME      = {{ env.APP_NAME }}"

      # Clear all env vars
      - name: clear_env
        desc: "Clear all environment variables"
        func: env_var
        do: clear

      # Restore from snapshot
      - name: restore_env
        desc: "Load env vars back from the saved snapshot"
        func: env_var
        do: load
        args:
          name: "before_deploy"

      - name: verify_restored
        desc: "Confirm the original vars are back"
        func: shell
        do: |
          echo "After restore:"
          echo "APP_NAME = {{ env.APP_NAME }}"
          echo "APP_PORT = {{ env.APP_PORT }}"

  # --- Security: sensitive vars are masked ---
  show_masking:
    desc: "Sensitive env vars are masked based on security policy"
    env:
      APP_NAME: "public-service"
      SECRET_KEY: "super-secret-value"
      AUTH_TOKEN: "tok_abc123"
    steps:
      - name: show_vars
        desc: "Safe vars show values; sensitive vars are masked with ***"
        func: shell
        do: |
          echo "APP_NAME   = {{ env.APP_NAME }}"
          echo "SECRET_KEY = {{ env.SECRET_KEY }}"
          echo "AUTH_TOKEN  = {{ env.AUTH_TOKEN }}"

  main:
    desc: "Run all environment variable demos"
    steps:
      - name: demo_task_env
        task: show_task_env

      - name: demo_env_var_function
        task: use_env_var_function

      - name: demo_masking
        task: show_masking

      - name: summary
        func: shell
        do: |
          echo "=== Environment Variable Features ==="
          echo "  - env:       Set vars on tasks or steps"
          echo "  - env_var:   Programmatic set / save / load / clear"
          echo "  - Security:  Mask sensitive vars in output"
          echo "  - Templates: Access with {{ env.VAR_NAME }}"