DeploymentAugust 17, 202611 min read

Blue-Green vs Rolling Deployments: What Zero Downtime Costs You

Both strategies promise your users never see the release. The difference that decides between them is how many versions of your code are talking to one database at the same time.

RThe Runsite Team

Both strategies are sold with the same sentence: your users never see the release. Both deliver on it most of the time, and that sentence is where the useful part of the comparison ends. Downtime is not what separates them. What separates them is what your application is doing during the minutes when two versions of it are alive, and what your data is exposed to while that lasts.

Search the comparison and you get a stable set of answers. Blue-green rolls back faster. Rolling costs less infrastructure. Canary is safer for a risky change. All true, and all of it about the code, which is the easy half. The half that decides is stateful, and most of the write-ups stop at the container boundary before they reach it.

How code gets from a push to a running container is a separate mechanic, worked through in git push to deploy, which stops deliberately at the moment traffic starts moving. This picks up at that moment.

Zero-downtime deployment is the promise both of them make

A blue-green release runs two complete copies of your application. One is live and serving everyone; the other holds the new version and serves nobody. When the new copy looks healthy, a router or load balancer moves all traffic across in one movement. The old copy stays running for a while afterwards, and that is what makes the rollback fast: you move the traffic back.

A rolling release has one copy and replaces it from the inside. Instances of the new version start a few at a time, each one is checked, and each one that passes takes over from an old instance, which then retires. There is no second environment and no single switch. The release is a sequence of small swaps that ends when the last old instance is gone.

A lot of the confusion in this comparison is vocabulary, so it pays to pin the names down. Blue-green, blue/green and blue–green are the same thing. Red/black is the same thing again, under the name Netflix used for it. A rolling update in Kubernetes vocabulary is a rolling deployment in everyone else's. The original write-up of the pattern is Martin Fowler's, from 2010, and it is still the description most of the current articles are paraphrasing.

Blue-greenRolling
What runs during the releaseTwo complete copies of the application, one live and one idleOne copy, with instances replaced a few at a time
How traffic movesAll at once, when the switch flipsGradually, as each new instance passes its check
Rolling backMove the traffic back; the old copy is still runningRun the swap again in reverse, one instance at a time
Peak infrastructureDouble, for the length of the releaseA little above normal, set by how many spare instances you allow
Versions serving at onceOne, apart from the moment of the switchTwo, for the whole release
What it needs from the platformControl of the router, and room for a second copyA health check the scheduler reads, and more than one instance
Every row describes the code. None of them describes the data underneath it, which is the part that decides.

"A rolling deployment is generally faster than a blue/green deployment"

That sentence comes from the AWS whitepaper on deployment options and it is the single most quoted line in this comparison, as of writing. It is also conditional in a way the quotation does not carry, which is why it has its own argument threads on Reddit from people who measured something else.

The condition is your startup cost. Blue-green pays the full boot of a second copy before any traffic moves, and pays it once, with every instance starting in parallel. Rolling pays a smaller boot repeatedly, in series, with a health check gate in the middle of every wave. Whichever total is smaller wins, and which one that is depends on a number about your application that the whitepaper does not know.

Take an application that needs 45 seconds to become ready: framework boot, a connection pool, a cache to populate. Across six instances rolled two at a time, that is three waves of 45 seconds plus the checks between them, so something over two minutes with the old version still serving throughout. Blue-green starts six instances at once, waits the same 45 seconds, and switches. The whitepaper's sentence holds cleanly for applications that are ready in a second or two. It stops holding for the one with a warmup, and the applications with warmups are the ones where release speed was a concern in the first place.

The case of a rolling update with 1 replica

A rolling release needs somewhere to roll. With a single instance there are only two moves available and neither of them is rolling. Start the replacement first and you are briefly running two copies, which is blue-green with n=1. Stop the old one first and there is a gap, which is downtime. Kubernetes names the second option honestly and calls it Recreate, as of writing.

This matters more than it sounds, because plenty of services never run more than one instance. A free tier, an internal tool, a staging environment, an API nobody has ever had to scale. On all of them the zero-downtime promise is kept by a mechanism that quietly needs one extra container for a few seconds, and whether the platform will run that container is the platform's decision. Find out which answer you have before a release finds out for you. The adjacent question of what a platform does with your containers when nobody is using the app is covered in what cold starts are and why free-tier apps fall asleep.

The axis nobody compares on: how many versions your database sees

Everything above is a statement about stateless code. The comparison tables are honest within that boundary and they all stop at it. The moment your application has a database, which is to say almost always, a second constraint arrives that neither strategy removes and only one of them is usually credited with.

Rolling puts two versions of your code in production simultaneously by design, and both of them talk to the same database. Blue-green is usually no different, because the second environment is a copy of the application and not a copy of the data. Most descriptions of the pattern list the shared database among its drawbacks and leave it there, as though the difficulty were the sharing. The difficulty is that a schema change now has to be workable for the code on its way out as well as the code arriving.

Which means the migration pattern is the same under both strategies. Add the new column before anything reads it, deploy code that writes to both, remove the old column in a later release. Expanding and then contracting, rather than renaming in place. That mechanic and the reasons for it are set out in git push to deploy; what the two strategies change is only how long the overlap lasts, not whether you need to plan for one.

The switch is instant. The schema is not.

Blue-green's selling point is that a rollback is one movement of traffic. That is true of your code and false of your data. A dropped column stays dropped, and the previous release, pointed at a schema it was never written for, can fail in ways that are harder to diagnose than whatever you were escaping. Decide the recovery path for a migration before you need it, not while the traffic is moving.

None of this argues against either strategy. It argues that the choice between them is a smaller decision than the one about your schema, and that a comparison which never mentions the database is comparing the easy part. Where the database itself lives and what the host takes off your hands is a separate question, covered in the jobs a managed Postgres solution absorbs for developers, and the database runs on managed PostgreSQL hosted in the EU.

What the health check decides, and what it only observes

A health check in a release is a gate, not a dashboard: the new version gets no traffic until it answers, and if it never answers, the traffic never moves. Both strategies share that principle. On a managed platform the contents of the check are frequently the only part of the release you control, so it deserves the attention the strategy debate usually absorbs.

Liveness and readiness answer different questions

Readiness asks whether this instance should receive traffic right now. Liveness asks whether this process is broken badly enough to be killed and started again. The names are Kubernetes vocabulary, as of writing, but every scheduler that gates a release has both concepts under some spelling.

Wiring the same URL to both is common and produces a specific bad afternoon. A dependency has a bad thirty seconds. Readiness fails, which is correct, and the instance stops taking traffic. Liveness fails too, because it is the same endpoint, so the container is killed and restarted, and a restart takes longer than the blip did. What should have been a brief withdrawal from the load balancer becomes a cycle of containers dying and booting while the dependency recovers on its own.

A check that only proves the port is open

The most common health endpoint in production is a route that returns 200 from a handler which touches nothing. It proves the HTTP server is listening. An application that booted with an unreachable database passes that gate, receives real traffic, and starts returning errors to real users while the deploy shows green in the dashboard.

A check that touches what the application needs to do its job fails instead, and the release stalls. That is the better outcome by a wide margin: a failed deploy with the old version still serving, rather than an outage with a successful deploy behind it. One cheap query and a ping to whatever cache you cannot serve without is usually enough.

There is a counterweight, and it bites in the opposite direction. A check that fans out to every downstream service turns any one of them having a bad minute into a failed deploy, and worse, into your instances being recycled during someone else's incident. The line that holds up is narrower than it first looks.

Check what this instance needs, not what the system needs

The endpoint should answer one question: can this container serve a request right now. Its own database connection and its own cache qualify. A third-party payment API, an email provider or a downstream team's service do not, however much you depend on them, because a check that includes them hands the decision to kill your containers to somebody else's uptime.

Interval, timeout, and the deploy that fails for the right reason

Three numbers decide how patient the gate is: how often the check runs, how long a single attempt is allowed to take, and how many consecutive failures count as a verdict. Set them too tight and an application that needs 40 seconds to warm never passes, every release fails, and the reasonable conclusion is that the platform is broken. Set them too loose and a genuinely broken release takes minutes to be caught, which on a rolling deployment means it has been quietly replacing healthy instances the whole time.

The starting number is your own measured cold start with headroom on top, not the platform default, which was chosen for an application nobody had seen. Measure it once on a cold container rather than a warm one, and set the timeout above the slowest of several runs rather than the average.

The blue-green deployment problem: cost, state, and the minutes in between

The cost objection is the one that gets raised first and it is real but bounded. Running two full production environments doubles the infrastructure you are paying for, and it does so for the length of the release rather than permanently. Where that bites depends on how you are billed. Metered by the second, doubling for four minutes is a rounding error. On fixed instances, the second environment may have to be owned all month for the few minutes a week it is used, which is a different conversation entirely.

The subtler cost is what the old copy is doing while it drains. Long-lived connections do not move when a router flips: websockets, server-sent events, a streaming response halfway through. You have two options and both are choices rather than defaults. Cut them, and users see a reconnect at a moment you chose. Wait for them, and the old environment stays alive and billable until the last one closes, which for a websocket application can be a long time after the switch.

Background work has the same shape and less visibility. If your workers ship in the same release, flipping traffic does not drain a queue. A job that the old version picked up one second before the switch finishes on the old version, against whatever schema the new release just applied. Scheduled work has the same exposure: a job that fires during the overlap can land on either side of it, or on both. Where that work runs and how it is triggered is its own decision, and scheduled jobs and cron on EU infrastructure is where it lives on Runsite.

Then there is a question the comparisons never raise, because they are written from inside a single region. Blue-green needs room for a second copy, and something has to find that room. If the platform satisfies it by placing the idle environment wherever there is capacity, then for the length of every release a complete copy of your production application is processing your production data somewhere you did not choose. For a hobby project that is a shrug. Under GDPR it is a transfer, and a transfer that lasts four minutes is still a transfer with the same paperwork behind it as a permanent one. The rules and what they require of a stack are covered in where to store EU user data.

The practical version of that is a question to ask a platform before you need the answer: not where the live environment runs, which every provider will tell you, but where the second one is allowed to appear.

Where canary fits, and why it answers a different question

A canary release sends a small share of traffic to the new version, watches what happens to it, and then either widens the share or withdraws it. It reads as a third option on the same list as blue-green and rolling, and it is not quite the same kind of thing.

What canary needs is not a router feature. Splitting traffic by percentage is the easy half and most routers do it. What it needs is the ability to look at error rate, latency and whatever your application counts as a bad outcome for that 5% specifically, held against the other 95%, and to decide from the difference. Without that comparison you have a rolling release with a longer pause in the middle and someone watching a graph that averages both versions together.

So the honest ordering is that blue-green and rolling are release mechanics, while canary is a release policy that runs on top of one of them and leans on observability you may not have. The difference between blue-green and canary, put plainly: blue-green moves everyone at once and bets on the checks that ran before the switch, canary moves a few people first and bets on being able to measure what happened to them. If you cannot answer whether the 5% is doing worse than the 95%, the option is not available to you yet, whatever the router supports.

Deployment strategies you can choose, and the ones you inherit

Nearly everything written about deployment strategies assumes you own the router. The Kubernetes documentation, the ArgoCD guides, the AWS whitepaper: all of it is written from the position of someone who configures a load balancer. On Kubernetes the entire strategy comes down to two numbers on the Deployment object, as of writing.

yaml
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1        # extra instances allowed above the desired count
    maxUnavailable: 0  # instances allowed to be missing during the release

With maxUnavailable at 0 and maxSurge at 1 you get a rolling release that never dips below full capacity, one instance at a time. Push maxSurge up to the size of the whole deployment and you have approximately blue-green, since a full second set starts before any of the first set retires. The alternative type is Recreate, which stops everything and then starts everything, and is the only one of the three that is honest about producing downtime.

On a managed platform you usually see none of that, and losing sleep over it is the wrong reaction. Those knobs come attached to a cluster that somebody has to maintain. What matters is which parts of the decision the platform kept and which it handed to you.

LayerUsually yoursUsually the platform's
Release strategy (rolling, blue-green, recreate)RarelyChosen for you, and often not named anywhere in the UI
Health check pathYes, and it is the highest-leverage thing you configure
Check interval and timeoutOftenSometimes fixed, sometimes only on paid plans
How many instances run during a releaseRarelyYes
Rollback to a previous releaseYes, when previous artifacts are keptWhether they are kept at all, and for how long
Where the second copy runsRarely visibleYes, and worth asking about explicitly
The question on a managed platform is not which strategy to pick. It is which one you already have, and which of its settings were left to you.

That reframing is most of the practical value here. Read your platform's release documentation and find out what happens to traffic during a deploy, whether a failed check stops the release or merely reports it, and what the platform does with the previous version once the new one is serving. Those three answers tell you which strategy you are running, whatever it is called. On EU-hosted web services with automatic rollback on a failed health check the answers are rolling, stops it, and keeps it, which is the shape most applications want without having to ask for it.

How Runsite handles it

Releases on Runsite's web services, with rolling deploys behind a configurable health check are rolling. New containers start alongside the ones already serving, traffic shifts once the health check passes, and a failed check returns to the previous healthy version instead of putting a broken build in front of users. The check path and its interval are yours to set, and that is the setting this article argues matters most.

The built image lives in Runsite's internal registry, while environment variables, secrets and health check configuration are attached to the deployment manifest separately from it, so configuration and artifact are not welded together at build time. Containers that fail are restarted automatically and rescheduled onto healthy nodes. Every pull request gets its own environment on its own URL without extra setup, and that is where a release you are nervous about should be seen first. Rollbacks and deploy history are on the Pro plan (€25/mo as of writing). A deploy takes around 30 seconds, with the full clone, build and release cycle usually under a minute as of writing. Servers are in Germany as of writing, so the question of where an extra copy of your application appears during a release has one answer instead of a shrug.

Setup details, including how to point the health check at something more useful than a bare 200, are in the Runsite docs.

The short version

  • Blue-green runs two complete copies and moves all traffic at once. Rolling runs one copy and replaces its instances a few at a time. Both promise zero downtime and both usually deliver it.
  • Rolling is faster when your application starts quickly, which is the condition the AWS whitepaper's much-quoted line leaves out. With a 45-second warmup, blue-green's parallel boot wins.
  • A rolling update with one replica is not a rolling update. It is either a brief second container or a gap, and which one you get is the platform's decision.
  • Both strategies put two versions of your code in front of one database. The schema has to work for the outgoing version as well as the incoming one, and rolling code back does not roll a schema back.
  • The health check is the part you usually control. Make it touch what this instance needs to serve a request, and nothing that belongs to somebody else's uptime.
  • Blue-green's real costs are the doubled infrastructure for the length of the release, the connections and jobs that do not move when traffic does, and the second copy appearing in a region you did not pick.
  • Canary is a policy rather than a mechanic. Without the ability to compare the small share against the large one, it is rolling with a longer pause.
  • On a managed platform the strategy is usually chosen for you. Find out which one you have, then spend your attention on the health check.
FAQ

Frequently Asked Questions

Common questions about this service.

Neither is better as a property of the strategy, and the question usually resolves on two things the comparison rarely names. The first is your startup time: rolling replaces instances in series with a health check between each wave, so an application that takes 45 seconds to warm spends several minutes mid-release, while blue-green boots a full second copy in parallel and pays that cost once. The second is capacity: rolling needs one spare instance, blue-green needs a complete duplicate for the length of the release, and on fixed-price plans that duplicate may have to be owned permanently. Underneath both sits the same constraint, which is that two versions of your code end up talking to one database, so the schema has to be workable for the outgoing version as well as the incoming one. On most managed platforms the strategy is chosen for you anyway, which makes the health check the decision that is genuinely yours.

Blue-green moves all traffic at once and stakes the release on the checks that ran before the switch. Canary moves a small share first, watches what happens to that share specifically, and then either widens it or withdraws it. The practical difference is what each one needs from you. Blue-green needs a router that can switch and room for a second environment. Canary needs the ability to compare error rate and latency for the 5% against the 95% and to decide from the difference, and that is an observability capability, not a routing one. Without it, splitting traffic by percentage gives you a rolling release with a longer pause in the middle. That is also why canary is better described as a policy running on top of a release mechanic than as a third mechanic beside the other two.

The switch itself is the safest part, since the previous environment is still running and moving traffic back is one action. The risk sits in everything that is not a stateless HTTP request. A schema change applied for the new version is not undone by moving traffic back, so an instant rollback of the code can leave the old version pointed at a database it was never written for. Long-lived connections such as websockets or streaming responses do not follow the router, so they are either cut at a moment you chose or the old environment stays alive until they close. Background jobs picked up just before the switch finish on the old version against the new schema. And if the platform places the second environment wherever it has capacity, a complete copy of your production processing runs in an unspecified region for the length of the release, which under GDPR is a transfer regardless of how briefly it lasts.

Three of them, in descending order of how often they are mentioned. The infrastructure cost is the obvious one: two full production environments run at the same time, so resource use doubles for the length of the release, and on fixed-price instances rather than per-second metering that second environment may need to be paid for all month. The shared database is the one that causes actual incidents, because the second environment is a copy of the application and not of the data, so both versions read and write the same schema and the fast rollback covers only the code. The third is drainage: connections, streams and in-flight background jobs do not move when a router flips, so the old environment either has to be cut off or kept alive until it empties. None of these is a reason to avoid the strategy, but a comparison that lists only the doubled cost has covered the cheapest of the three.

Your app deserves to be online

Free to start. Deploy in under a minute. No credit card needed.