BLOG/BY ROLE
THEME
BY ROLE

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.

Apr 22, 2026 OrchStep Team 6 minROLE: Backend DeveloperSCALE: Any
RUNNABLE DEMO
Full source for this post: blog/backend-dev-tasks
VIEW SOURCE

Most 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."

orchstep.yml
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.0

The 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 menu

You 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 test

See 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-run

You 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

ConcernBeforeOrchStep
"Did I migrate?"re-run and hopeone migrate task, callable from dev
Rollback stepslooked up each timea catch: block next to apply
Flaky integration testsre-run by handretry: { max_attempts: 3, backoff_rate: 2.0 }
Onboardingstale READMEorchstep menu lists every task
Per-service driftthree different dev.shone 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

Got a scripts/dev.sh you'd retire if this looked good? Run orchstep init in the repo and try it on yours.

#BACKEND#TASK-RUNNER#MIGRATIONS#TESTING
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — BY ROLE