Externalize env config into files
When your inline environments block stops fitting on a screen, move each target into its own file. The filename becomes the hierarchy — defaults, group, group-env — and --env picks the path.
blog/externalize-env-configInline environments are great until they aren't. A nonprod/prod split with three targets fits in one orchstep.yml and reads at a glance. But add a few instances, give each environment a dozen values, and let three different people own dev, staging, and prod — and that single block becomes a merge-conflict magnet nobody wants to touch.
The fix is not a different model, just a different home. Point the workflow at a directory and let each target be its own file. The values are identical to the inline version; they just live where the person who owns them can edit one small file without rebasing the whole workflow.
The filename is the hierarchy
This is the part that makes it click: the filename encodes the layering. Running --env nonprod-dev loads defaults.yml, then nonprod.yml, then nonprod-dev.yml — in that order, later files overriding earlier ones. You do not declare the hierarchy anywhere; you name it.
name: externalized
# env config lives in files under environments/. The filename IS the hierarchy.
env_config:
env_dir: environments
tasks:
deploy:
steps:
- name: ship
func: shell
do: 'echo "deploy {{ vars.version }} to {{ vars.target }} (mon={{ vars.monitoring }} replicas={{ vars.replicas }})"'Each file is a flat map of values — no group: or vars: nesting like the inline style needs. The nesting is the filename. A nonprod-staging.yml is understood to layer on top of nonprod.yml, which layers on top of defaults.yml.
What resolves to what
orchstep run deploy # version=2.4.0 replicas=1 (defaults.yml only)
orchstep run deploy --env nonprod-dev # version=2.4.0-dev target=dev-cluster mon=basic
orchstep run deploy --env nonprod-staging # replicas=4 target=staging-cluster version=2.4.0-rc1
orchstep run deploy --env prod-production # replicas=10 target=prod-cluster mon=fullLook at nonprod-staging: replicas is 4 because the staging file overrides the 2 from the nonprod group, which overrode the 1 in defaults. Three files, one resolved value, last-writer-wins. The prod-production run never reads anything under nonprod — --env only walks the chain its name describes.
The naming patterns that hold up
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.ymlA few rules that keep this from sprawling:
- Two groups beat five.
nonprod/prodcovers most estates. A group per environment is just inline environments wearing a directory. - Instances are for sharded things — a replica, a region, a tenant:
prod-production-eu1.yml. They are the most specific file, so they win. - Keep credentials out. These files belong in git. Inject secrets at runtime — that is a separate namespace entirely.
You can skip the
env_config:block when the directory is literally namedenvironments/and sits next to the workflow — OrchStep auto-detects it. Declareenv_config: { env_dir: ... }only to point at a differently-named folder.
Inline or external — same model
| Inline (Style 1) | External (Style 2) | |
|---|---|---|
| Lives in | one orchstep.yml block | one file per target under environments/ |
| Hierarchy expressed by | group: / vars: nesting | the filename (<group>-<env>.yml) |
| Best when | a handful of small environments | many targets, or split ownership |
| Selected with | --env <name> | --env <name> |
They are the same precedence ladder — defaults < group < environment < --var — just stored differently. You can start inline and externalize later without touching a single step, because the steps only ever read {{ vars.X }}.
Catch the gaps before you run
Externalizing introduces one new failure mode: a value present in one environment's file but forgotten in another. A run then renders it as an empty <no value>. orchstep validate turns that into a static coverage matrix across every environment — worth a post of its own.
Where to go next
- Environments & Variable Files — the external
env_configstyle in full - One workflow, every environment — the inline style this builds on
orchstep validate— coverage checking for env files
The full tree above is runnable in orchstep-demos. The steps only echo — drop in your real deploy and --env does the rest.
curl -fsSL https://orchstep.dev/install.sh | sh