OrchStep for scale-ups
Multiple services, three or four environments, and a deploy that has to behave differently in production than in staging. Here's how OrchStep keeps one workflow honest across every environment.
At scale-up size the shape of the problem changes. You're past "does it deploy" and into "does it deploy the same way to staging and production, every time, run by people who didn't write it." You have several services, three or four environments, and a deploy that genuinely needs to behave differently in prod — more monitoring, a manual approval — without becoming a different script per target.
The failure mode here is environment drift: a deploy-staging.sh and a deploy-prod.sh that started identical and quietly forked. One gets a fix the other doesn't, and the gap between them is exactly where the 2 a.m. incident lives. The job at this stage is to keep one workflow and let the environment supply the differences.
What automation looks like at this stage
You've outgrown one flat orchstep.yml with a single target. You want named environments that bundle their own settings — monitoring level, whether approval is automatic — selected at the command line. OrchStep's inline environments do exactly this: shared values live in an env_groups family, each environment selects a group and overrides a few vars, and you pick one with --env.
One deploy, many environments
Here nonprod and prod are groups; staging and production are concrete environments that inherit from them. The deploy task is written once. The gate reads approval — a value that comes from the environment, not from a branch in the code — so production holds for approval while staging auto-promotes, with zero duplicated steps.
name: platform
defaults:
version: "3.4.0"
artifact: ghcr.io/acme/api
# Shared settings for a family of environments.
env_groups:
nonprod:
vars: { monitoring: basic, approval: auto }
prod:
vars: { monitoring: full, approval: manual }
# Concrete targets. Each selects a group, overrides a few vars.
environments:
staging:
group: nonprod
vars: { target: staging-cluster }
production:
group: prod
vars: { target: prod-cluster }
tasks:
# orchstep run deploy --env production
deploy:
steps:
- name: build
func: shell
do: echo "building {{ vars.artifact }}:{{ vars.version }}"
- name: push
func: shell
do: echo "pushing {{ vars.artifact }}:{{ vars.version }} ({{ vars.monitoring }} monitoring)"
retry:
max_attempts: 3
interval: "2s"
backoff_rate: 2.0
- name: gate
if: '{{ eq vars.approval "manual" }}'
then:
- name: hold
func: shell
do: echo "{{ vars.target }} requires approval before promotion"
else:
- name: auto
func: shell
do: echo "auto-promoting to {{ vars.target }}"Run it against either environment and the only thing you change is the flag:
orchstep run deploy --env staging # basic monitoring, auto-promote
orchstep run deploy --env production # full monitoring, holds for approvalSame build, same push, same retry. The behavioral difference between staging and production is data in the environment block, not a forked script — so the two can't drift, because there's only one.
Catch differences before they ship
With more environments comes more ways to be surprised. The dry run resolves a specific environment and prints the exact plan, so you can confirm production really does take the approval branch before you run it:
orchstep run deploy --env production --dry-runYou can read the resolved monitoring: full and the hold step in the output. That's a real review artifact — paste it in a PR — instead of "trust me, prod is different."
What you gained
| Concern | A script per environment | One workflow + environments |
|---|---|---|
| Staging vs prod logic | two diverging scripts | one task, values from --env |
| Environment drift | inevitable | impossible — single definition |
| Prod approval | a separate prod script | if: gate on an env-supplied var |
| Flaky registry push | re-run by hand | retry: with backoff |
| "Is prod actually different?" | read both scripts | --dry-run --env production |
| Adding an environment | copy a whole script | a few lines under environments: |
Where OrchStep is not the answer
If you've already standardized on a deployment controller — Argo CD, Spinnaker, a real progressive-delivery system — let it own rollout strategy and health gating; OrchStep shouldn't reimplement that. Where it fits at scale-up is the orchestration around those tools: the build, the promotion logic, the per-environment differences your team runs by hand and in CI. Use it for the glue, not as a replacement for a delivery platform you already trust.
Where to go next
- Environments — groups, selection, and the two styles
- Variables & Outputs — precedence and overrides
- Previewing with Dry Run — resolve a specific environment
- Error Handling — retry, catch, finally
One orchstep.yml, every environment. Define your targets under environments: and switch with --env.
curl -fsSL https://orchstep.dev/install.sh | sh