The first deploy is a sequence of small manual steps you half remember. The tenth one is the same sequence, plus whatever you forgot on the fourth. Somewhere in there it stops being a task and becomes a script, and the project gets much calmer.

The part people skip is the other direction. Nearly every deploy guide ends at "your site is live" and says nothing about the ten minutes after a bad one.

The idea

A deploy is a repeatable procedure with a check at the end and a known way back. Repeatable means it is written down as commands, so it runs the same way at 9am and at 11pm. The check is one request that proves the new version is actually serving. The way back is a previous version you can restore without rebuilding anything.

code
   git push
      |
      v
  +---------+     +-------+     +---------+     +--------------+
  |  build  | --> | tests | --> | release | --> | health check |
  +---------+     +-------+     +---------+     +--------------+
                                     |                 |
                              keep the old        200 OK ? --> done
                              release on disk          |
                                     ^            not OK ?
                                     |                 |
                                     +----- roll back <+
                                       (point at the
                                        previous build)

How it works

  • Managed platforms already do all of this. Vercel, Netlify, Railway and Render build every push, keep every previous build, and have a button labelled something like Instant Rollback that reactivates one. Rolling back there takes about fifteen seconds and no terminal.
  • On your own server you build the same shape by hand. A deploy.sh that pulls, builds into a new dated folder, moves a symlink, restarts the service, then curls the site. Keeping the previous folder is what makes rollback possible, and it is the step everyone drops to save disk space.
  • A is a route that touches something real. A homepage that was pre-rendered at build time answers fine while the database is down. /api/health that runs SELECT 1 does not.
  • Database migrations do not roll back with the code. Restoring the previous build undoes your application. A column you dropped is still dropped. This asymmetry is the whole reason schema changes get treated more carefully than code changes.

What to do

  1. Write the deploy down the first time you do it. Even as a numbered list in your project's README. Then ask your assistant to turn that list into scripts/deploy.sh, and read it before you run it.
  2. Practise the rollback while nothing is wrong. Deploy, roll back, deploy again. Ten minutes now, on a calm afternoon, buys you a procedure you already know during the one hour you will actually need it. Nobody learns a rollback for the first time under pressure and does it well.
  3. End every deploy with the same check. curl -sS https://myapp.com/api/health and look at the answer. A deploy can finish cleanly and still leave a broken app behind it.
  4. Make destructive schema changes in two deploys. Add the new column, ship, backfill it, ship again, and only drop the old one once the previous version is no longer running anywhere.

Where it breaks

Deploying on a Friday is a real argument with a boring answer: it depends on how fast you can undo it. With one-button rollback and a health check, Friday is an ordinary day. With a hand-typed deploy you have never reversed, you are betting the weekend on a build you have not watched. What decides it is whether someone is around to notice a problem and able to fix it, which for a solo builder means asking what you want your Saturday to look like.

Two things a rollback will not save you from. Anything that already left the building, like emails sent or payments taken, cannot be recalled by restoring the old code. And a rollback restores the previous version of your app, not the previous state of the world: caches, queues and half-finished background jobs stay where they were.