Two full environments for the same service, both able to serve, with a router deciding which one receives traffic. The new version is deployed to the idle colour and runs the entire pipeline — provision, migrate, deploy, smoke test — while the live colour keeps serving. When the tests pass, the release happens as a routing change.

That routing change is the whole product. Because nothing is rebuilt at release time, rollback is another routing change back to the colour that is still warm, and readiness is decided before any user sees the new code. The constraint is that only the router is switched: the database, the schema, and every connection already open stay exactly where they were.

The switch is the release

The pattern needs two environments, not two copies of one configuration. The idle colour is provisioned and healthy at the same capacity as the live one, so the pipeline has somewhere real to run. Nothing about the release touches the serving colour until the moment of the switch.

That moment is a single decision point: a DNS record, a load balancer backend, an ingress route. Whether it is atomic depends on how the routing layer applies the change, not on the pattern’s name. Pushing the change to five router instances that each apply it on their own schedule produced a measured 59 ms window in which some instances served the old colour and some the new, with 47 of 282 sampled states mixed; flipping one shared pointer produced a single state change and no mixed window at all.

Rollback is a pointer move

Because the old colour still runs the previous version, recovery is bounded by the routing change rather than by a rebuild and redeploy. That is what makes a bad release cheap, and it holds only while the old colour is still correct — which a forward schema migration can quietly take away.

The price is standing double capacity: two full environments, monitored, patched and paid for, one of them idle at any moment. That is a budget decision rather than a technical one, and it is the honest reason blue/green gives way to a rolling update when fast rollback is not worth 100% overhead.

The database does not have two colours

Both colours talk to the same store, so schema is not something blue/green can isolate: a migration that only the new colour understands is an outage for the colour still serving. The safe shape is expand and contract — add the new column additively, let both colours run against a schema they both understand, and drop the old one only after the last old connection is gone.

That last condition is the one teams get wrong, because the drain window is set by the longest-lived connection rather than by the router. In a model of 200 connections opened before the switch, the old colour was still answering 7448 requests after the flip, continuing until the oldest connection’s 2.0 second lifetime expired.

When it breaks

The classic failure is a switch that is not atomic across all instances, so both colours serve traffic for a brief window. It presents as a small percentage of requests returning the wrong version during a deploy — a page that looks like the previous release, a field missing from a response — and it is invisible unless the response says which colour served it. Log the colour with every request, then look for a mixed window around the deploy timestamp.

The second failure is the shared schema. A migration that renames a column is fine for the new colour and fatal for the old one, and the blast radius is data rather than errors: the old colour keeps writing through a path whose meaning has changed. Long-lived connections make it worse, because the old colour keeps accepting writes for the entire drain window after the flip.

Check it quickly

Confirm which colour the router points at, that both colours are ready, and that live responses agree:

kubectl -n prod get deploy -l app=checkout -o custom-columns=NAME:.metadata.name,VERSION:.metadata.labels.version,READY:.status.readyReplicas
kubectl -n prod get ingress checkout -o jsonpath='{.spec.rules[0].http.paths[0].backend.service.name}{"\n"}'
for i in $(seq 1 50); do curl -sS https://checkout.example.com/-/version; done | sort | uniq -c

The ingress must name a colour whose deployment is fully ready, and all fifty samples must report one version. Two versions in the sample mean the switch was not atomic; a colour named by the ingress with a zero ready count means the switch will take the site down.

Sources