Nothing we build reaches production without clearing a 72-item launch checklist, stage-gated across four phases, where every item closes on proof rather than a promise. The list isn't the point — plenty of teams have a launch doc nobody reads. The discipline is: an item is only checked when someone can point at the evidence that it's true. A backup job that's scheduled doesn't count; a backup we restored into a scratch database and queried does. A security header we set in Caddy doesn't count; a curl of the response showing the header does. That single rule — proof, not intent — is what turns a checklist from theater into a gate.
Here's how the four gates work, what each one refuses to let through, and the two closes that catch the most real incidents.
Four gates, not one big list
A flat 72-item list is unreadable and gets skimmed. We group the items into four gates, and a build can't enter a gate until the previous one is fully closed:
- 01Correctness — the app does what the spec says: critical user flows exercised end-to-end, error and empty states handled, inputs validated at the boundary, no unhandled promise rejections in the console.
- 02Hardening — security and privacy: headers set and verified, CSP enforced (not just Report-Only), session cookies Secure/HttpOnly/SameSite, no secrets in the bundle, privacy policy matching what the page actually loads.
- 03Operability — you can run it in production: structured logging, error tracking wired to a live DSN, uptime checks on key URLs, backups taken and restore-tested, a written rollback path.
- 04Discoverability — it can be found and measured: unique titles and canonicals, sitemap of only 200-status canonical URLs, robots.txt that allows the right crawlers, analytics collecting, Core Web Vitals inside budget.
Ordering matters. There's no point tuning LCP on a page that still throws in a core flow, and no point hardening headers on an app that can't be rolled back. Correctness first, discoverability last.
Proof-or-it-didn't-happen closes
The close condition is written into each item as the artifact that proves it. A few examples from the real list:
- "Security headers present" closes on a saved curl -I of the production response showing HSTS, X-Content-Type-Options, Referrer-Policy, and the CSP — not on 'we added them to the Caddyfile.'
- "Backups work" closes on a timestamped restore into a throwaway database and a row count, not on a green cron job.
- "Rollback tested" closes on an actual redeploy of the previous image in staging, not on 'we tag our releases.'
- "Analytics live" closes on the event showing up in the dashboard after a real click, not on the script being in the head.
Scheduled is not tested. A backup you have never restored is a hypothesis, and production is a bad place to find out it was wrong.
The restore test is the one that has saved us the most grief. A backup that's running on a schedule feels like safety, but until you've restored it you don't actually know whether the dump is complete, whether the credentials still work, or whether the file is quietly zero bytes because a volume path changed three deploys ago. We restore into a scratch database and run a couple of representative queries. It takes minutes and it converts a hope into a fact.
Why the discipline beats the tooling
You could automate a lot of this — and we automate what's stable, like header checks and Lighthouse budgets in CI. But the gate isn't a CI job; it's a person willing to say 'not yet' with a specific missing artifact in hand. Automation tells you a header is absent. Judgment tells you the CSP is technically present but so loose it's decorative. The checklist exists to make that judgment routine and legible: anyone on the team can look at a gate and see exactly which artifact is missing and who owns getting it.
It also changes how the team writes code in the first place. When you know 'backups work' closes on a restore, you design the backup to be restorable from day one. When you know 'rollback tested' closes on an actual redeploy, you keep the previous image tagged and the migrations reversible. The gate pulls quality upstream — which is the entire point of having one.
What it costs, and what it's worth
The honest cost is that launches take longer and some of the items feel like bureaucracy right up until the day one of them catches a missing environment variable, an unreversible migration, or a staging robots.txt about to ship a Disallow: / to production. We've had each of those caught at a gate. None of them would have been caught by 'looks good, ship it.' The checklist is cheap insurance against the specific, boring, expensive failures that take a production app down at the worst possible time.