← All posts

My Pre-Deployment Checklist

What actually goes into shipping a production release when you're the only one who can break it.

There's a version of deployment discipline that comes from working at companies with deployment windows and change management boards and multiple people who need to sign off before anything hits production. I've never worked at one of those companies. My discipline came from breaking things myself, usually on a Friday afternoon, usually for a real user.

The checklist I'm going to describe isn't a formal process document. It's closer to a pre-flight habit—things I do before I push because I've learned, sometimes expensively, what happens when I don't.

The mental model first

I think about deployment as a one-way door. Once something is in production, the state of the world has changed. Migrations ran. Emails went out. Cache keys shifted. Users saw something new. Rolling back is possible in the abstract but rarely clean in practice. So the work happens before the door, not after.

This changes how I approach the checklist. I'm not trying to catch every possible bug. I'm trying to make sure I understand what I'm actually deploying, that I've thought through the failure modes that matter, and that if something goes wrong, I can diagnose it quickly.

Know what's in the release

This sounds obvious but it's easy to skip. I keep a local notepad (sometimes literal paper) of everything that changed since the last deployment. Not every commit—the meaningful changes. New API endpoint. Schema migration. Third-party integration update. New environment variable.

The reason to do this manually rather than just reading the diff is that writing it out forces me to think about dependencies. If I changed how Stripe webhook data gets stored, that touches the payment flow. If I updated a Prisma schema, that means a migration has to run before the new code goes live. If I added a new environment variable, it needs to exist on the deployment target before the deploy, not after.

I've shipped a bad deploy because I forgot to add a new Resend API key to the Vercel environment before pushing. The code deployed fine. The first time someone triggered an email, it blew up. Now "new env vars" is the first thing on the list.

Migrations before code

If the release includes a database migration, the migration runs first. Not at the same time as the deployment. Before it.

With Neon and Prisma, I run prisma migrate deploy manually against the production database before I push the code that depends on it. Yes, this means there's a brief window where old code is running against a new schema. I design migrations to be backward compatible for exactly this reason—additive changes, no immediate column drops, nullable fields before you fill them with data.

The order matters because if the migration fails partway through, the old code is still running and users aren't affected. If the code deploys first and the migration fails, you've got new code pointing at an outdated schema, and now you're dealing with errors in production while trying to debug a migration in parallel. That's a bad place to be.

Check the obvious things in staging

I have a staging environment for Composer Catalog. I don't always use it as rigorously as I should. But before any deploy that touches the core user flow—auth, billing, the primary feature—I go through it manually.

Not an automated test suite (though those run too). I mean I actually open the browser and do the thing a user would do. Create an account. Trigger the thing the new feature is supposed to do. Check that the thing it was doing before still works.

Automated tests catch regressions in the paths you've tested. Manual walkthroughs catch the thing you changed that you forgot was connected to something else. Both matter. For solo work, the manual pass is the one that catches the real surprises.

Deployment timing

I don't deploy on Fridays. This is the one rule I hold almost religiously. If something breaks Friday afternoon, I'm debugging it over the weekend or I'm leaving users with a broken experience until Monday. Neither is acceptable and both are avoidable.

Tuesday through Thursday morning is my deployment window. Things are quiet enough that I can monitor after pushing, busy enough that I'll catch user-facing issues quickly rather than finding out about them days later.

For Vercel deployments, the actual push takes a few minutes. I stay at my desk and watch the build complete, check the deployment URL, and then go through a quick smoke test on production before I consider the deploy done. "It deployed" and "it's working" are different things.

What I watch for right after

The first fifteen minutes after a deploy are the high-alert window. I have logs open. For anything that touches payments or user data, I watch the Stripe dashboard and check that events are coming through as expected.

What I'm looking for: errors spiking in the logs, any indication that a core flow is broken, unexpected webhook failures. If something is clearly wrong, I know what I deployed (because I wrote it down) so I can either roll back the code or figure out if the issue is fixable with a quick follow-up deploy.

Most of the time nothing bad happens. But the discipline of watching means I catch problems in minutes instead of hours.

Communicate before, not after

If a deploy changes something visible—a new feature, a changed behavior, a UI update—I tell the relevant people before I push, not after. For Composer Catalog, that means Keith knows what's coming and when.

This isn't about getting permission. It's about not surprising someone with a change they weren't expecting. If they're in the middle of using the product and the interface shifts, that's a bad experience even if the change is an improvement. A quick heads-up before I push takes thirty seconds and prevents that.

The thing the checklist can't do

None of this prevents bugs. Code ships with bugs. The checklist is about reducing the risk of catastrophic failures—migrations that corrupt data, deploys that take down the auth flow, errors that silently swallow user actions. The ordinary bugs that make it through are easier to fix than the ones that hit production at 3pm on a Thursday and take the whole feature down.

The checklist also doesn't replace judgment. Some deploys are low-risk enough that I push and move on. Others get more ceremony. Knowing which is which is most of the job.

What it does do is make sure I'm making that judgment intentionally, not just pushing because the code looks good and I'm impatient to see it live.