Blue-green deployment has a clean story: keep serving on blue, start green, health-check green, cut over, keep blue warm for rollback. I have automated that pattern for Remix commerce apps with PM2 slots, webhook triggers, and GitHub Actions. The story is clean. Production is not.

The happy path hides the real checklist

A working script often does something like:

  1. Pull the target branch
  2. Install dependencies
  3. Run migrations
  4. Build
  5. Boot green on a second port
  6. Poll /health until pass/fail
  7. Move traffic / restart blue onto the new build
  8. Stop green

Every line has a footnote.

Migrations are the silent killer

If a migration is not forward-compatible, green can boot while blue is still serving old code against a new schema, or worse, green migrates and blue starts erroring mid-cutover. Blue-green does not remove migration discipline; it exposes the lack of it.

Rules that keep you honest:

  • Expand/contract migrations over multiple deploys when possible
  • Never deploy a migration that old code cannot read/write safely
  • Treat “migrate in the deploy script” as a product decision, not a default

Health checks lie if you let them

A process that returns 200 on /health may still be:

  • connected to the wrong database
  • missing env vars loaded only in blue’s process manager config
  • unable to reach Shopify Admin API
  • serving a build that crashes on the first authenticated route

Useful health checks verify dependencies you actually need. Overly strict checks flap. Overly weak checks bless broken greys as green.

Traffic movement is an abstraction

On a single VM with PM2, “cut over” might mean restart blue after green is proven, or flip a reverse proxy upstream. On multi-node setups, sticky sessions, websocket-like connections, and in-memory state make cutover semantics messier. Blue-green is a family of strategies, not one command.

Webhooks and concurrency complicate deploys

Commerce apps receive Shopify webhooks while you deploy. During cutover you can see:

  • duplicate deliveries to blue and green
  • webhook handlers on old code writing old assumptions
  • background jobs locking rows the new code migrated

You need idempotent webhook handlers anyway. Deploys make that requirement visible.

Rollback is a product feature

If rollback means “hope the previous Docker layer is still around,” you do not have rollback. You have optimism. Keep the previous slot runnable long enough to revert. Practice the revert. Measure how long it takes.

What I learned to document

For every app that uses blue-green, I want a short runbook:

  • What does healthy mean?
  • What migrations are allowed in this deploy?
  • How do we pause webhook processing if needed?
  • How do we confirm cutover beyond HTTP 200?
  • Who is on call during the window?

Blue-green without operational clarity is theater. Blue-green with boring runbooks is how releases become uneventful: the highest compliment in production engineering.

← Back to blog