Blue-Green vs Canary Deploys, Explained
Blue-green and canary get discussed as if they're two points on a maturity ladder, with canary at the top. They aren't. They protect against different failures, cost very different amounts to operate, and the one most teams should implement first is usually the one they skip.
I'm Ajay, I build PandaStack, where every app deploy is blue-green — so I have a view, and I'll be explicit about where it's a limitation rather than a design win.
Blue-green: two versions, one switch
You run the current version (blue). To deploy, you stand up the new version (green) completely separately, verify it, and then move all traffic at once. Blue stays around briefly so rollback is another switch rather than another deploy.
The properties that matter:
- The switch is atomic. Every request is served by exactly one version — no window where two versions are handling traffic simultaneously.
- Verification happens before any user is exposed. Green can be health-checked, smoke-tested and prodded while blue keeps serving.
- Rollback is seconds, because the old version is still running. This is the biggest practical benefit and the one people underrate.
- You pay for both versions during the overlap. Brief, but real.
What blue-green catches is a broken deployment: the build that fails, the app that won't start, the missing environment variable, the port nothing listens on. That is the overwhelming majority of deploy failures, and blue-green makes them invisible to users because green never becomes live.
What it does not catch is a deployment that starts perfectly and is subtly wrong. It boots, it answers the health check, and it returns incorrect results or degrades under real traffic. Blue-green will happily promote that.
Canary: a fraction of traffic, then more
You deploy the new version alongside the old and route a small share of traffic to it — 1%, then 5%, then 25%, then everything — watching metrics at each step and rolling back if they degrade.
The properties:
- Blast radius is bounded. A bad version affects 1% of requests instead of 100%.
- You get real production signal — real traffic shapes, real data, real concurrency — that no staging environment reproduces.
- Both versions serve simultaneously, which your system must tolerate. This is a bigger constraint than it sounds.
- It needs infrastructure: weighted routing, per-version metrics, and an automated decision about whether to proceed.
What canary catches is the subtly wrong version — a performance regression, an elevated error rate on a specific code path, a memory leak that takes twenty minutes to show. These are the failures blue-green promotes cheerfully.
The cost nobody mentions
Canary's real price isn't the routing layer. It's that two versions of your application are live at the same time, against one database, for as long as the rollout takes. That imposes constraints on how you write changes:
- Every schema migration must be backward-compatible, because the old version is still reading and writing. No renaming a column in one step, ever.
- Every API change must be additive. The old version might handle the retry of a request the new version started.
- Caches shared between versions must not carry version-specific formats, or one version will read the other's data and misinterpret it.
- Background jobs enqueued by one version may be consumed by the other, so job payloads need the same compatibility discipline.
Blue-green has a much weaker version of the same requirement — the switch is atomic, but there's still a brief overlap and in-flight requests to drain — so you need one-step-compatible migrations there too. Just not for the full duration of a gradual rollout.
The part that does most of the work
Here's the observation I'd most want to land: the majority of the safety in either strategy comes from the health check, and most health checks are bad.
A health check that returns 200 as long as the HTTP server is listening proves the process started. It does not prove the app works. It will pass with a database it can't reach, a missing configuration value, and a broken cache client — and then blue-green will promote it or canary will send it real traffic.
// A readiness check that actually gates a promotion. Every dependency
// the first user request will touch gets touched here first, with a
// hard timeout so a hung check fails rather than hanging the deploy.
app.get("/readyz", async (req, res) => {
const checks = {};
const deadline = (p, ms) =>
Promise.race([p, new Promise((_, r) => setTimeout(() => r(new Error("timeout")), ms))]);
try {
await deadline(db.query("SELECT 1"), 2000);
checks.database = "ok";
} catch (e) {
checks.database = e.message;
}
try {
// Not just "is the config object present" -- is the value usable.
if (!process.env.STRIPE_API_KEY) throw new Error("missing");
checks.config = "ok";
} catch (e) {
checks.config = e.message;
}
const healthy = Object.values(checks).every((v) => v === "ok");
res.status(healthy ? 200 : 503).json({ ok: healthy, checks });
});Upgrading a liveness check into a real readiness check is a couple of hours of work and eliminates more bad deploys than a traffic-splitting layer will. Do it first regardless of which strategy you're running.
Which one you need
Blue-green is enough when your deploy failures are the loud kind, your traffic volume is too low for 1% to be a statistically useful sample, or you don't have per-version metrics to make a canary decision with. That describes most teams. Ten requests per second means a 1% canary sees six requests a minute — you cannot detect an error-rate regression from that, so the canary is theatre.
Canary earns its keep when you have enough traffic for a small percentage to be meaningful, when your failures are performance and correctness regressions rather than crashes, and when you have the metrics and automation to decide without a human staring at a dashboard. A canary that requires someone to watch Grafana for twenty minutes is a manual process wearing a costume.
How it works on PandaStack
We do blue-green, and only blue-green. A deploy provisions a brand-new microVM, clones the repo, installs, builds and starts your app there, then health-checks the real application port. Only if that succeeds does the app's routing flip to the new VM — a single atomic update — after which the previous deployment is marked superseded and its VM is torn down. If the health check never passes, the old version keeps serving and the deployment is marked failed.
Rollback is the same mechanism in reverse, which is why it's fast:
# Redeploy the previous (or a specific) deployment.
curl -X POST https://api.pandastack.ai/v1/apps/$APP_ID/rollback \
-H "Authorization: Bearer $PANDASTACK_API_KEY"We do not have weighted traffic splitting, so there is no native canary today. If you need one, the practical pattern is two apps — stable and canary — with your own edge splitting traffic between their URLs, or feature flags inside a single app, which I'd try first. I'd rather say that plainly than describe blue-green as if it were a deliberate rejection of progressive delivery; it's where we are.
The summary
Blue-green catches broken deploys and gives you a fast rollback, for the cost of running two versions briefly. Canary catches subtly wrong deploys and bounds their blast radius, for the cost of running two versions simultaneously against one database — which means every schema and API change becomes a backward-compatibility exercise. Most teams should start with blue-green, spend the afternoon making the readiness check real, and reach for feature flags before traffic splitting. Add canary when you have the traffic volume for a small percentage to mean something and the automation to act on it without a human watching.
Frequently asked questions
What is the difference between blue-green and canary deployment?
Blue-green stands up the new version completely separately, verifies it, then moves all traffic at once — the switch is atomic, so every request is served by exactly one version. Canary runs both versions simultaneously and shifts traffic gradually, watching metrics at each step. Blue-green protects against a broken deploy that fails to start; canary protects against a deploy that starts fine but is subtly wrong under real traffic. They solve different problems, and canary is not simply the more advanced option.
Do I need canary deployments?
Probably not yet, and traffic volume is the deciding factor. A 1% canary at ten requests per second sees six requests a minute, which cannot tell you anything statistically useful about an error rate — so the canary is ceremony rather than protection. You need canary when you have enough traffic for a small percentage to be a meaningful sample, when your failures are performance and correctness regressions rather than crashes, and when you have automation that decides whether to proceed without a human watching a dashboard.
What does canary deployment require from my database migrations?
Strict backward compatibility for the whole rollout window, because the old version is still reading and writing while the new one runs. That means no renaming a column in one step — you add the new column, deploy code that writes both and reads the new one, backfill, then drop the old column in a later release. The same applies to API changes, cached data formats and background job payloads. This compatibility discipline, not the routing layer, is the real cost of adopting canary.
Is a health check enough to make deploys safe?
A good one does most of the work, and most health checks are not good ones. A check that returns 200 as long as the HTTP server is listening proves the process started and nothing else — it passes with an unreachable database, a missing config value and a broken cache client. A readiness check that actually touches every dependency the first real request will touch, with hard timeouts so a hung check fails rather than hangs, prevents more bad deploys than a traffic-splitting layer does, and it takes an afternoon to write.
Can I do canary deploys on PandaStack?
Not natively — deploys are blue-green, with an atomic flip after the new microVM passes a health check on its real application port, and there is no weighted traffic splitting today. If you need canary behaviour, two practical patterns work: run a stable app and a canary app and split traffic between their URLs at your own edge, or use feature flags inside a single app so you roll out behaviour gradually rather than deployments. The feature-flag version is usually the better answer anyway, because you can turn a bad feature off without redeploying.
Keep reading
49ms p50 cold start. Fork, snapshot, and scale to zero.