Built, measured and torn down on 5 August 2026 in us-east-1. Total cost $0.20. The infrastructure no longer exists — the code and the raw samples behind every number are in the repo.
The setup
Two Auto Scaling Groups behind one load balancer, each on its own launch template version and target group. Traffic moves by changing a single listener action. Then the live environment gets upgraded in place with an Instance Refresh.
Each instance serves a plain-text endpoint naming its environment, version and instance ID, which is what makes any of this measurable from a shell loop rather than a guess.
Request path: request loop → ALB listener → blue or green target group. Deploy paths: Terraform switches the listener; an instance refresh rolls the live group in place. Failure path: an out-of-band terminate-instances.
Three scenarios, one fleet
Blue → green cutover. 62 requests, all 200:
Rolling instance refresh. 43 requests across a 430-second roll, all 200:
Abrupt termination. An in-service instance killed out of band, 638 requests at ~5/s:
The finding
Only the unplanned change lost requests. 0%, 0%, then 4%.
The difference is deregistration. A planned change tells the load balancer to stop using a target and lets in-flight connections finish — that’s deregistration_delay, one line of configuration. An out-of-band terminate-instances gives it no warning, so the balancer keeps routing to a dead host until its own health checks fail. Two checks at a 10-second interval matches the observed 16-sample gap before the first 502.
This is the argument for connection draining and lifecycle hooks, and it’s the one result here that a diagram wouldn’t have told you.
Two things that surprised me
“Instant rollback” is instant to issue, not to take effect. The cutover lost nothing, but 44 requests were still served by the old environment after the switch was issued — a listener change propagates over roughly 40 seconds. If you’re rolling back because production is broken, users keep hitting the broken version for another half-minute. That’s a number worth knowing before you promise one in an incident review.
A rolling deploy serves mixed versions by definition. Both v1.0 and v1.1 answered during the seven-minute roll. That isn’t a defect — it’s what rolling is, and it’s the constraint blue/green doesn’t have. If a release changes an API contract or a session format, consecutive versions have to be compatible or you can’t roll at all. Blue/green has no such requirement, and that’s what the double capacity buys you.
Also worth knowing: pointing an ASG at a new launch template version changes nothing until a refresh runs. The template is desired state, not running state.
The same thing as code
The stack is also written as Terraform, where the interesting part is what happens to the cutover. In the shell version it’s an imperative call. In Terraform it’s one variable:
terraform apply -var active_environment=green
var.active_environment is read by exactly one resource — the listener. Both launch templates, both target groups, both ASGs and the load balancer are identical either way. The switch becomes a one-resource in-place update and the rollback is the same command with the other value.
Blue/green stops being a procedure you have to execute correctly and becomes something the configuration guarantees.
Links
- s4-blue-green — scripts, Terraform, full write-up
- Raw samples — the three logs charted above
- Three ways to survive a region failure — where this sits against real DR