~/blog $ git show 2026-09-01
Every Monday I Ship to Nobody
· 5 min read · Victor Benavides
--ops--craftEvery Monday evening I run a production release. A recent one promoted eighteen services, four of them reaching production for the first time in their lives. Before it, a code freeze. Before that, a Saturday spent working through a written test plan. On the night itself, a runbook with an explicit ordering and a checklist of about thirty-five boxes.
The platform this ships has no paying customers in production yet.
Written down like that it sounds like a man in an empty restaurant rehearsing table service. I want to make the case that it isn't — and be straight about what it costs.
What the train actually is
Releases go out weekly, Monday evening, and each one is a lettered state: everything that was live before, plus a specific set of changes, deployed together. "Is this feature live?" has one authoritative answer, and it isn't a git log.
A few days before, work freezes. Saturday is the test gate — a written plan, real personas, real devices, walked by a human. Monday evening the runbook runs in a fixed order, and anything risky goes out switched off.
Four documents, one cadence, and none of it is automated ceremony. It's all decisions written down before the night they're needed.
The freeze isn't discipline, it's arithmetic
Here's the part that isn't a preference.
I don't promote features. I promote branches. When a service ships, everything merged into its development branch goes with it — not the piece I was thinking about, all of it.
That single mechanical fact is where the freeze comes from. Anything merged after the cutoff rides along whether or not I meant it to, tested or not. Without a freeze, the question "what's in this release?" has no stable answer at the moment I most need one. With a freeze, the set is closed before it's tested, and the thing I tested on Saturday is the thing that ships on Monday.
People describe freezes as caution. Mine is bookkeeping.
Deploying and releasing are two different verbs
The most useful habit I've picked up is separating the code is running in production from the behaviour is switched on.
Anything with real blast radius ships dark: deployed, wired up, and inert. Turning it on becomes a separate decision on a separate day, made deliberately, with the deploy already proven. If it misbehaves the fix is a config change, not a rollback at eleven at night.
This costs almost nothing to build and it converts the scariest category of release into two ordinary ones.
The question I couldn't answer
The reason any of this exists is embarrassing, so here it is.
Somebody asked me whether a particular feature was live in production, and I could not answer with confidence. I knew it was merged. I was fairly sure it had deployed. Whether it was actually running and verified — I'd have had to go and look.
Merged, deployed, and verified are three different states, and I had been collapsing them into one word: "done." That's how you end up assuming a protection is on when it isn't, or rebuilding something that already shipped.
So now there's a ledger, and a feature is only live when it's been checked against production — not merged, not deployed, checked — with a note of how. "Built but not deployed" and "on dev only" are first-class states with names, because pretending they were live is exactly where the pain came from.
Why before the customers, and not after
The obvious objection is that this is overkill for a product nobody's paying for yet, and the objection has real force. I'd answer it two ways.
The first is that a process you've run three times is boring, and boring is the only useful state for a process on the night it finally matters. Nobody learns a release procedure during their first real incident. You learn it now, while the cost of getting it wrong is a redeploy and an hour of your evening.
The second is that it's already earning. The freeze has caught changes that would have ridden along untested. The Saturday walkthrough has found things that passed every automated check. The ledger has corrected me — more than once — when I was about to build something that already existed, or trust something that wasn't actually on.
None of those needed a customer to be worth catching.
What it costs
It is slower, and I want to say that plainly rather than pretend the discipline is free.
Work sits finished on a development branch for up to a week before anyone can use it. That's genuinely frustrating on a Wednesday when something small and obviously safe is done and just... waits. I've thought about carving out an exception for small changes, and haven't, because "obviously safe" is precisely the judgement I'd want a process to protect me from on a bad day.
There's also a real amount of writing. The runbook and the checklist take an afternoon, every week, and no user will ever see them.
I pay both deliberately. Not because it makes releases exciting — because it makes them boring, on purpose, before the week when I'll badly need them to be.