Multi-region configuration
Three regions, one artifact, one config file. Keep per-region overrides in environments, loop over the region list to fan out, and stop copy-pasting deploy steps you have to keep in sync.
blog/multi-region-configGoing multi-region is where config sprawl gets real. You started with one deploy. Now you have deploy-us.sh, deploy-eu.sh, and deploy-apac.sh — three files that are 90% identical and 10% drifted, because someone bumped the replica count in us last quarter and never touched the others.
The replica count, the DNS endpoint, the cluster name: those genuinely differ per region. The steps do not. So the trick is to keep the differences in config and the steps in one place.
One region's overrides, not one region's script
OrchStep environments let you name a region, attach its overrides, and select it at the CLI. Shared policy goes in an env_groups: block so your primary region and your standby regions don't each repeat the same settings.
name: multi-region
defaults:
app: api
version: "2.1.0"
regions:
- us-east-1
- eu-west-1
- ap-southeast-1
# Shared policy for a family of regions.
env_groups:
standard:
vars: { replicas: "3", tier: standard }
primary:
vars: { replicas: "6", tier: primary }
# Per-region overrides. Each selects a group and sets its own endpoint.
environments:
us-east-1:
group: primary
vars: { region: us-east-1, endpoint: api.us.example.com }
eu-west-1:
group: standard
vars: { region: eu-west-1, endpoint: api.eu.example.com }
ap-southeast-1:
group: standard
vars: { region: ap-southeast-1, endpoint: api.ap.example.com }
tasks:
# orchstep run deploy --env eu-west-1
deploy:
steps:
- name: plan
func: shell
do: echo "deploying {{ vars.app }}:{{ vars.version }} to {{ vars.region }} ({{ vars.replicas }} replicas, {{ vars.tier }} tier)"
- name: dns
func: shell
do: echo "pointing {{ vars.endpoint }} at {{ vars.region }}"us-east-1 is the primary, so it pulls replicas: 6 from the primary group. The other two share standard. Add a fourth region tomorrow and it's four lines, not a fourth script.
orchstep run deploy --env eu-west-1The region, replicas, and endpoint are all resolved from the environment you picked. Nothing is positional, nothing is copy-pasted.
Fan out across every region
Selecting one region is the per-region path. The other thing you do constantly is the same thing everywhere — a coordinated rollout. That's a loop over the region list, not a third script:
# orchstep run rollout
rollout:
steps:
- name: each
loop:
items: '{{ vars.regions }}'
func: shell
do: echo "rolling out {{ vars.app }}:{{ vars.version }} to {{ loop.item }}"
- name: done
func: shell
do: echo "all {{ len vars.regions }} regions updated to {{ vars.version }}"The region list lives in vars.regions — one source of truth that both the loop and your docs read from. There's no way for the rollout to skip a region that the per-region config knows about, because they share the same list.
What you gained
| Concern | Three scripts | OrchStep |
|---|---|---|
| Region differences | drift across files | environments: overrides |
| Shared policy | duplicated per region | one env_groups: entry |
| Deploy everywhere | run three scripts in order | loop over vars.regions |
| Add a region | copy a script, edit it | add one environments: entry |
| Preview a region | read the script | --env <name> --dry-run |
Where this is overkill
If you genuinely run in one region and have no plans to add another, you don't need an environment matrix — a couple of --var overrides are plenty. This pattern earns its keep the moment "the same change, everywhere" becomes a recurring sentence in your standups.
Where to go next
- Environments — groups, per-env overrides, and
--env - Variables & Outputs — the four-level scoping model
- Previewing with Dry Run — see the resolved plan before it runs
The full project above is a runnable demo — echo-only, so it's safe to try. Swap the echos for your real deploy commands when the shape looks right.
curl -fsSL https://orchstep.dev/install.sh | sh