OrchStep for backend developers
The inner loop of backend work is a pile of half-remembered commands: bring up the database, migrate, seed, run the integration suite. Here's how to make that loop one readable workflow you can run, preview, and trust.
blog/backend-dev-tasksMost of a backend developer's day isn't writing handlers. It's the connective tissue around them: start Postgres and Redis, apply the migrations someone added on main, seed enough data to log in, then run the integration suite — which flakes once in five because a container wasn't ready.
Every service does this differently. One repo has a Makefile, another has scripts/dev.sh, a third has a paragraph in the README that's three commits out of date. New teammates lose a day. You lose ten minutes every context switch.
This post turns that inner loop into a single OrchStep workflow — the same docker, psql, and go test you already run, but ordered, named, and previewable. No platform, no daemon. One binary that reads a YAML file.
The pain, concretely
- "Did I run the new migration?" You re-run it to be sure, and hope it's idempotent.
- The integration suite fails on a cold start; you re-run it and it passes. Nobody's sure if that's a bug or a race.
- A rollback after a bad migration is three commands you look up every time.
The workflow
Three tasks cover the loop: dev (stand the service up), migrate (apply with a rollback path), and test (unit plus a retrying integration suite). dev calls migrate as a step, so there's exactly one definition of "apply the migrations."
name: orders-service
defaults:
service: orders
db_url: "postgres://app@db:5432/orders"
tasks:
# `orchstep run dev`
dev:
steps:
- name: deps
func: shell
do: echo "starting postgres + redis for {{ vars.service }}"
- name: migrate
task: migrate # call another task as a step
- name: seed
func: shell
do: echo "seeding dev data into {{ vars.db_url }}"
- name: serve
func: shell
do: echo "running {{ vars.service }} on :8080"
# `orchstep run migrate`
migrate:
steps:
- name: plan
func: shell
do: echo "pending migrations for {{ vars.db_url }}"
- name: apply
func: shell
do: echo "applying migrations to {{ vars.service }}"
catch:
- name: rollback
func: shell
do: echo "rolling back last migration batch"
# `orchstep run test`
test:
steps:
- name: unit
func: shell
do: echo "running unit tests for {{ vars.service }}"
- name: integration
func: shell
do: echo "running integration suite for {{ vars.service }}"
retry:
max_attempts: 3
interval: "2s"
backoff_rate: 2.0The catch: block under apply is your rollback — it runs only if the migration fails, so you stop looking up the rollback command every incident. The integration retry: gives the cold-start race three attempts with exponential backoff instead of you mashing the up-arrow. That's the difference between "the suite is flaky" and "the suite tolerates a slow container start, on purpose."
Run it without remembering the task name
orchstep menuYou get a fuzzy task picker with single-key hotkeys, and it refuses to hang in a non-interactive shell — so the same test task is safe to call from CI. When you do know what you want:
orchstep run dev
orchstep run testSee the migration before it touches the database
The part worth its weight on a Friday afternoon: a dry-run resolves variables and prints the exact plan — including the rollback path — without executing anything.
orchstep run migrate --dry-runYou can read what apply will do and confirm a rollback exists before you run it for real. Full tour: Previewing with Dry Run.
What you actually gained
| Concern | Before | OrchStep |
|---|---|---|
| "Did I migrate?" | re-run and hope | one migrate task, callable from dev |
| Rollback steps | looked up each time | a catch: block next to apply |
| Flaky integration tests | re-run by hand | retry: { max_attempts: 3, backoff_rate: 2.0 } |
| Onboarding | stale README | orchstep menu lists every task |
| Per-service drift | three different dev.sh | one orchstep.yml shape everywhere |
None of this hides the shell. docker, psql, and go test still do the work — OrchStep gives the plumbing a shape so the next person can read it. If a four-line Makefile already covers your service, keep it. The moment it grows guards, retries, and a rollback, this is the readable version.
Where to go next
- Quick Start — your first workflow in two minutes
- Variables & Outputs — the four-level scoping model
- Error Handling — retry, catch, finally, timeouts
- Composition — calling tasks from tasks
Got a scripts/dev.sh you'd retire if this looked good? Run orchstep init in the repo and try it on yours.
curl -fsSL https://orchstep.dev/install.sh | sh