---
title: Deploying to Production
description: Everything to check before, during, and after shipping a change to a production web service.
category: software
tags: [software, operations]
version: "1.0"
updated: 2026-07-07
sources:
  - name: Google SRE Book — Release Engineering
    url: https://sre.google/sre-book/release-engineering/
  - name: Martin Fowler — Feature Toggles (Pete Hodgson)
    url: https://martinfowler.com/articles/feature-toggles.html
---

For shipping a change to a production web service with real users.
Assumes you have CI, at least one pre-production environment, and
dashboards for errors and latency.

## Before you deploy

- [ ] Confirm CI is green on the exact commit you are deploying `{essential}`
  The commit, not the branch. A merge landed after your last CI run
  can ship code no test has ever seen.
- [ ] Skim the full diff between production and the release candidate
  What's going out is often more than your change — everything merged
  since the last deploy rides along.
- [ ] Write down the rollback plan `{essential}`
  The exact command, the version identifier you'd roll back to, who
  runs it, and how long it takes. If the plan is "redeploy the old
  image", verify that image still exists in the registry.
- [ ] Verify the migration works with the currently running code `{when: database-migration}` `{essential}`
  Old code and new schema run side by side during rollout. Use
  expand/contract: add columns as nullable now, backfill, and drop
  the old ones in a later deploy — never in the same one.
- [ ] Rehearse the migration against a production-sized dataset `{when: database-migration}` `{essential}`
  An `ALTER TABLE` that is instant in staging can lock a
  100-million-row table for minutes. Measure it before production does.
- [ ] Put the new behavior behind a feature flag, deployed off `{when: risky-change}`
  Separates deploy from release. Flipping a flag back takes seconds;
  even a good rollback takes minutes.
- [ ] Pick a deploy window when people can respond
  Not Friday afternoon, not ten minutes before you leave, not peak
  traffic. The best window is early in a workday with the author and
  on-call both around.
- [ ] Announce the deploy in the team channel
  What's going out, a link to the diff, and who is driving. This is
  also what turns "is anyone deploying?" from a mystery into a scroll.
- [ ] Post a maintenance window to the status page `{when: expected-downtime}`
  Customers forgive planned downtime and punish surprise downtime.

## During the deploy

- [ ] Run the migration before rolling out the new code `{when: database-migration}` `{essential}`
  New code that expects a schema that isn't there yet fails loudly and
  everywhere at once.
- [ ] Roll out to a canary slice first
  5–10% of traffic, then compare error rate and latency against the
  rest of the fleet before going wide. Most bad deploys announce
  themselves within minutes at 5%.
- [ ] Watch error and latency dashboards while instances cycle
  Have them open *before* the rollout starts so you can see the
  before/after edge, not just the after.
- [ ] Verify the new version is actually serving
  Hit the version or build-hash endpoint. "Deploy succeeded" sometimes
  means the orchestrator gave up and left old containers running.
- [ ] Smoke-test the critical path by hand
  Log in, perform the core action users pay for, log out. Two minutes,
  catches the class of failure health checks can't see.
- [ ] Roll back at the first sign of user-facing errors `{essential}`
  Roll back first, diagnose later. Debugging forward in production
  turns a five-minute incident into an hour-long one — the rollback
  plan you wrote costs nothing to use.

## First hour after

- [ ] Enable the flag for an internal or small cohort first `{when: risky-change}`
  Employees, then 1–5% of users, then everyone. Each step is a chance
  to catch what staging couldn't.
- [ ] Keep watching for at least 30 minutes
  Some failures need a cache expiry, a cron run, or a traffic peak to
  surface. A deploy isn't done when the progress bar finishes.
- [ ] Compare key business metrics against the same window yesterday
  Errors can be silent: a signup flow that drops 30% of users throws
  no exceptions.
- [ ] Check background jobs and queue depths
  A worker crashing on the new code shows up as a growing queue, not
  an alert — a slow-motion outage.
- [ ] Announce completion in the same channel
  Include anything you noticed; the next deployer inherits your context.

## Follow-up

- [ ] Remove the feature flag once the change is fully rolled out `{when: risky-change}`
  A stale flag is an untested code path and a config landmine. Put a
  removal ticket in the sprint the day you flip it to 100%.
- [ ] Schedule the contract step of the migration `{when: database-migration}`
  Drop the old columns and code paths after a safe soak period, or
  they'll still be there in five years.
- [ ] Update the runbook with anything this deploy taught you
  If a step surprised you, it will surprise the next person at 3 a.m.
