BLOG/BY ROLE
THEME
BY ROLE

OrchStep for release managers

Cut, ship, verify, roll back — the release checklist as code, with retries on the flaky publish and an automatic rollback in a catch block when verification fails.

Apr 9, 2026 OrchStep Team 6 minROLE: Release ManagerSCALE: Any
RUNNABLE DEMO
Full source for this post: blog/release-pipeline
VIEW SOURCE

Release day is a sequence held together by attention. Generate the changelog, tag the version, publish to the registry (which flakes, so retry), run the post-deploy smoke, and if it's bad, roll back — fast, correctly, under pressure. Miss a step or fat-finger the rollback and a routine release becomes an incident.

The steps are always the same. The order is always the same. The only thing that changes is the version and the target. That's exactly the kind of thing you should not be running from memory at the end of a long day.

This post writes the release as an OrchStep workflow: the happy path, the retry on the part that flakes, and the rollback as structure rather than a command you hope you remember.

The release, as a workflow

cut produces the changelog and tag and hands the ref forward via an output. ship runs cut, publishes with retries, verifies, and — on failure — rolls back in a catch: and always notifies in finally:.

orchstep.yml
name: release-train
# The release manager's checklist as code: cut -> ship -> verify -> rollback.
defaults:
  version: "1.4.0"
  tier: staging

tasks:
  # `orchstep run cut --var version=1.5.0`
  cut:
    steps:
      - name: changelog
        func: shell
        do: echo "generating CHANGELOG for {{ vars.version }}"
      - name: tag
        func: shell
        do: echo "tagging v{{ vars.version }}"
        outputs:
          ref: "v{{ vars.version }}"
      - name: announce
        func: shell
        do: echo "cut {{ steps.tag.ref }}"

  # `orchstep run ship --var version=1.5.0 --var tier=production`
  ship:
    steps:
      - name: cut
        task: cut
      - name: publish
        func: shell
        do: echo "publishing {{ vars.version }} to {{ vars.tier }}"
        retry:
          max_attempts: 3
          interval: "1s"
          backoff_rate: 2.0
      - name: verify
        func: shell
        do: echo "post-deploy smoke on {{ vars.tier }}"
        catch:
          - name: rollback
            func: shell
            do: echo "rolling {{ vars.tier }} back to the previous release"
        finally:
          - name: notify
            func: shell
            do: echo "release {{ vars.version }} -> {{ vars.tier }} finished"

The three load-bearing pieces:

  • outputs: on the tag, referenced later via steps.tag.ref — the ref flows through the pipeline instead of being retyped.
  • retry: on publish — three attempts with exponential backoff for the registry that flakes, written as four lines instead of a hand-rolled loop.
  • catch: is the rollback — if verify fails, the rollback runs automatically; it's part of the release, not a command you have to remember under pressure. finally: notifies either way.

Cut, then ship

orchstep run cut  --var version=1.5.0
orchstep run ship --var version=1.5.0 --var tier=production

And the thing every release manager wants on the morning of a big release — see the whole plan, resolved, executing nothing:

orchstep run ship --var version=1.5.0 --var tier=production --dry-run

You can confirm the version is right, the target is right, and the rollback path exists before you trigger it. That's the difference between "I'm pretty sure rollback works" and seeing it in the plan.

What you gained

ConcernBeforeWith OrchStep
The versionretyped at each stepone --var, flows via outputs
Flaky publishmanual re-runretry: { max_attempts: 3, backoff_rate: 2.0 }
Rollbacka command you hope to recallcatch: runs it automatically
Notify on outcomeeasy to forgetfinally: always runs
"Will this do the right thing?"trust and pray--dry-run prints the plan

This doesn't replace your registry, your tagging, or your deploy target — they still do the work. It replaces the coordination that currently lives in your head and a wiki page. If your release is genuinely one command, keep it. When it's a sequence with a flaky middle and a rollback you rehearse, make the sequence the artifact.

Where to go next

Write your next release down as cut and ship, dry-run it the morning of, and stop holding the rollback in your head.

#RELEASE#ROLLBACK#VERSIONING#CI
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — BY ROLE