All writing

Releases Should Be Boring

Shipping software has somehow turned into a ceremony. Release trains. Readiness checklists. A bot that comments on every PR. Sign-offs in three different Slack channels. A change-management ticket nobody reads. And to top it off, a deploy that’s gated behind whether some JIRA ticket happens to be sitting in the right column.

For the last few years I’ve been quietly arguing for the opposite. At a few of my last companies I pushed for a release process so plain it almost feels like cheating, and it worked every time. Nobody missed the ceremony.

The Process

Here’s the whole loop:

  • The next version already exists in JIRA from the moment the last one shipped. Say the current release is Scanner Launcher v1.0.25. It sits there as the upcoming (“Next”) version, waiting.
  • A JIRA automation rule stamps that version onto a ticket’s Fix Version field the instant the ticket moves to Done. Nobody remembers to do it, because nobody does it. No tracking spreadsheet, no parallel “release scope” board.
  • main is always releasable. Unfinished work is dark behind feature flags, and the PR pipeline catches problems before they ever reach main, so “is it ready?” stops being a question you pause to ask.
  • When you want to ship, you promote: dev, then stage, then production. Two clicks in Spinnaker. The only gate is technical readiness, so stage has to be green before prod.
  • The production pipeline finishing is the trigger for everything else. On success it tags the release in git, and that tag is the source of truth.
  • JIRA is synced from that tag: the version gets bumped, v1.0.25 is marked Released, and the next “Next” is created. Because it’s driven off the tag instead of coordinated by hand, the two systems can’t drift apart.

Then you do it again.

What’s missing is the interesting part. There’s no manual gate blocking the deploy. No required checklist with twenty boxes. No human in the middle deciding whether the tickets “look right” before the pipeline is allowed to run. The bookkeeping rides along as a side effect, and the deploy happens because someone clicked deploy.

The one tax this model takes on is flag debt. Keeping main always green leans hard on feature flags, and flags that nobody ever retires are their own quiet kind of ceremony. That’s the spot where the complexity you squeezed out of releases tends to sneak back in, so it’s worth a cleanup habit. But it lives in the code, not on the release path, which is exactly where I’d rather deal with it.

What Everyone Else Seems To Build

The teams I’ve watched go the other way didn’t sit down and decide to make releases painful. Every step looked sensible the day it got added. Stack enough of them together, though, and a deploy turns into an obstacle course.

A few of the usual suspects:

  • JIRA validation gates. The pipeline won’t deploy unless every ticket in the build is in a specific state, has a Fix Version, has a linked PR, and has been blessed by QA. Sounds responsible right up until a hotfix needs to go out and the one ticket somebody forgot to drag into “Ready for Release” is now holding up a production fix in the middle of an incident.
  • Auto-generated release notes. The pipeline scrapes commit messages or ticket titles and spits out notes that read like a robot wrote them, because one did. Somebody rewrites them by hand before they reach customers anyway. So you automated the part nobody minded doing and kept the part that actually needs a human.
  • Multi-stage Slack approvals. The deploy waits on a thumbs-up from the lead, then the PM, then someone in QA. Three people, three calendars, three time zones. The approval is usually a rubber stamp. The waiting isn’t.
  • Change-management tickets for every deploy. A process built for rare, risky changes gets bolted onto a routine push, so now shipping a copy fix needs the same paperwork as a database migration.
  • Rigid release trains. Miss the Thursday cut-off and your finished, tested, reviewed work sits around for a week. So people cram half-baked changes in before the window closes, which is exactly what you were trying to avoid.
  • Magic keywords in commit messages. At one place, every commit needed a specific tag so the release-notes bot knew whether to include it or leave it out. Forget the keyword and the build was perfectly happy, the deploy went out fine, and the feature just quietly never showed up in the notes. A loud failure you can fix. This one shipped wrong output that looked correct until someone noticed a bugfix was missing weeks later.
  • Trigger-happy auto-rollback. Wire it up to a flaky smoke test and it’ll roll back a perfectly good deploy at 2am, then page someone to put it back.

None of these started out as a bad idea. The problem is that they pile up, and they pile up on the critical path. The release can’t move until each one is happy, every one of them has edge cases, and the edge cases show up right when you’re under pressure to ship.

It’s the same rot I wrote about in A Case Against Abstraction. A layer gets added to “decouple” or to “be safe,” and the combined effect is that nothing moves until you’ve peeled back four wrappers. Release tooling goes the same way code does if you let it.

Automation Isn’t The Problem

This is usually where people hear something I’m not saying, so let me head it off. I’m not against automation. I care a lot about fast pipelines. Automate the boring, repeatable, error-prone stuff all day long.

What matters is where the automation sits.

Good automation rides on top of the release, while bad automation stands in front of it and blocks it. The deployment itself, getting tested code from stage to production, should never be blocked by something like JIRA validation. JIRA is a record of what you’re doing. It shouldn’t be the thing that decides whether your software is allowed to run.

So syncing Fix Versions, posting a release summary to a channel, bumping the version, tagging the commit, marking the version Released: all fine. They ride alongside the deploy, and if one of them fails you fix it afterwards while production keeps running. Refusing to deploy because a ticket is in the wrong column, or because the generated notes didn’t pass some lint rule: not fine. You’ve put a paperwork problem in the way of a production change.

The test I actually use is whether I can still ship if this automation breaks. If I can’t, it probably doesn’t belong on the critical path, and it’s bookkeeping that wandered into the cockpit.

The Two-Click Release

The bar I hold things to is pretty low on purpose. In a healthy setup a deploy is two clicks in Spinnaker: pick the version, promote it through the environments, done. The release notes and the Fix Versions and the JIRA bookkeeping all fall into place around that, instead of standing in its way.

That isn’t a lower standard. The tests still run. The environments still gate on whether things are technically ready, so stage has to be green before prod. What’s gone is the ceremony: the approvals that rubber-stamp, the checklists that get half-filled-in, the validations that exist to make a release feel rigorous rather than actually be safe.

Boring is the point. When shipping is a two-click non-event, people ship smaller and more often, and that does more to reduce risk than any checklist ever has.

Earn Every Step

I’m not telling you to rip all of this out tomorrow. Some gates exist for real reasons: compliance, regulated environments, changes that can genuinely take down the business. The goal isn’t zero process. It’s that every step on the release path should have to earn its spot, the same way every abstraction in your code should.

So the next time you’re about to add a step, ask whether it really needs to be on the critical path. Either the release is genuinely unsafe without it, or it just feels less official. If your deploy breaks the day that automation breaks, you haven’t made shipping safer. You’ve just made it harder.

These days I won’t run a release any other way. Two clicks, and the bookkeeping takes care of itself.

Email address copied hello@darkotasevski.dev