DOC_INDEX
THEME
DOCS/Learn OrchStep/Environments & Variable Files

Environments & Variable Files

env_groups and environments inline or as external files - one workflow, many targets, selected with --env

Environment separation in OrchStep is declarative: shared values go in groups, per-environment values in environment definitions, and you pick the target at the CLI with --env. Steps stay environment-agnostic. This chapter covers both styles - inline blocks and externalized variable files - plus the naming patterns that keep them maintainable.

Style 1: inline blocks

name: env-demo

defaults:
  replicas: "1"
  db_host: "localhost"

env_groups:
  nonprod:
    vars:
      replicas: "2"
      log_level: "debug"
  prod:
    vars:
      replicas: "10"
      log_level: "error"

environments:
  dev:
    group: nonprod
    vars:
      db_host: "dev-db"
  staging:
    group: nonprod
    vars:
      db_host: "staging-db"
      replicas: "4"        # env overrides its group
  production:
    group: prod
    vars:
      db_host: "prod-db"

tasks:
  show:
    steps:
      - name: print
        func: shell
        do: 'echo "replicas={{ vars.replicas }} db={{ vars.db_host }}"'
orchstep run show --env dev          # replicas=2  db=dev-db
orchstep run show --env staging      # replicas=4  db=staging-db
orchstep run show --env production   # replicas=10 db=prod-db
orchstep run show --env production --var replicas=99   # CLI still wins

Inline is right for a handful of environments. When the blocks grow - or when different people own different environments - externalize them.

Style 2: external environment files (env_config)

Point the workflow at a directory; each environment becomes a file. The filename IS the hierarchy: running --env nonprod-dev-instance34 loads defaults.yml, then nonprod.yml, then nonprod-dev.yml, then nonprod-dev-instance34.yml - later overrides earlier. Each file is a map of values (scalars or whole objects), with no group:/vars: nesting. Click through the layout:

environments/ is the default - env_config is optional. If your directory is named environments/ and sits next to the workflow, you can omit the env_config: block entirely - OrchStep auto-detects it. Declare env_config: { env_dir: ... } only to point at a differently-named directory. An explicit env_config: always wins, and an inline environments: block takes precedence over the convention.

orchstep.yml
name: env-demo
env_config:
  env_dir: environments

Resolution with those exact files (later overrides earlier):

orchstep run show                              # replicas=1  db=localhost   (defaults.yml)
orchstep run show --env nonprod                # replicas=2  log=debug
orchstep run show --env nonprod-dev            # replicas=2  db=dev-db
orchstep run show --env nonprod-dev-instance34 # replicas=1  db=dev-db-34   (instance wins)
orchstep run show --env prod-production        # replicas=10 db=prod-db

Tip: --env selects the target and --var always wins over it, so a developer can pin a single value for one run - orchstep run show --env production --var replicas=1 - without editing any file.

Naming patterns that scale

defaults.yml                     # org-wide baseline
<group>.yml                      # nonprod.yml, prod.yml
<group>-<env>.yml                # nonprod-dev.yml, prod-production.yml
<group>-<env>-<instance>.yml     # nonprod-staging-db645.yml

Guidelines that hold up in practice:

  • Two groups beat five. nonprod/prod covers most estates; resist a group per environment (that is just environments again).
  • Instances are for sharded things - a database replica, a region, a tenant: prod-production-eu1.yml.
  • Keep secrets out. These files belong in git; inject secrets at runtime via --var, --vars-file pointing outside the repo, or OS env ({{ env.SECRET }}).

Style 3: ad-hoc variable files (--vars-file)

Independent of environments, any flat YAML file can be loaded at run time:

# overrides/load-test.yml
replicas: "50"
log_level: "warn"
orchstep run deploy --env staging --vars-file overrides/load-test.yml

Convention: keep them in an overrides/ directory named by purpose (load-test.yml, dr-failover.yml, local-dev.yml) - they answer "what is different about THIS run", while environments/ answers "what is different about this TARGET".

The full precedence ladder

defaults  <  group  <  environment (<instance)  <  task vars  <  step vars  <  --vars-file  <  --var

Verify before you run: orchstep validate

A group is a shared layer, not always a complete target. With Style 2 it is easy to leave a variable unset for one environment - a run then renders it as an empty <no value>. orchstep validate catches this statically: it scans every vars.X a task references and reports which would be unresolved under each environment, as a coverage matrix.

Take a deploy task that references version and target, with the env files from the naming pattern above:

# orchstep.yml
tasks:
  deploy:
    steps:
      - { name: ship, func: shell, do: 'echo "deploy {{ vars.version }} to {{ vars.target }}"' }
environments/
  defaults.yml          version: "2.4.0"     # every environment inherits this
  nonprod.yml           # group layer — sets shared vars, but NOT target
  nonprod-dev.yml       target: dev-cluster   # concrete environment
  prod.yml              # group layer — NOT target
  prod-production.yml   target: prod-cluster  # concrete environment

version lives in defaults.yml (so it resolves everywhere); target is only in the concrete *-dev / *-production files. validate shows exactly that:

$ orchstep validate deploy

coverage matrix (✓ supplied · absent):

  variable    (none)  nonprod  nonprod-dev  prod  prod-production
  target
  version

findings:
 nonprod : 1 unresolved (target)

Here target is supplied by nonprod-dev/prod-production (concrete environments) but not by the nonprod/prod group layers - exactly what you want to know before selecting one. If a group file supplies everything (a single shared config applied to many instances), it shows complete and is a valid target. Use --env <name> for a per-step report, and --strict to fail CI when a referenced variable is supplied by no environment. See orchstep validate.

See it in a real pipeline

Environment Promotion deploys a service through dev/staging/production with the inline style plus approval gates and health checks.

Or clone a runnable example: serve-multi-envs shows the external env_config: style (one file per environment under environments/), and serve-demo shows the inline style - both in github.com/orchstep/orchstep-demos.