The image is built, the tests pass — one last step remains: the new code meets real users. This is exactly where the risk hides. Release strategies answer two questions: how many users will notice a problem and how fast you can get back.
Deploys used to be "all or nothing"
Historically a deploy meant: stop the server, upload the new version, start it. While the swap is in progress, the service is unavailable. If something goes wrong, rolling back takes as long as the deploy itself. All users see the problem at the same time.
Modern strategies solve this in different ways. Let's look at each one from scratch.
Rolling update — replace one replica at a time
Problem: a full swap makes the service unavailable for a while, and on a large fleet of servers that is unacceptable.
Solution: replicas are replaced one by one, not all at once. While one replica restarts with the new version, the others keep serving requests. The service's capacity barely dips.
Kubernetes does this out of the box: it won't send traffic to a new replica until it passes a readiness check. If a new replica starts with errors, the rolling update stops on its own.
What rolling can't do:
- instant rollback (a rollback is just another rolling update in reverse, and takes minutes);
- protection against errors that only show up under full traffic;
- control over "who exactly sees the new version".
An important condition: during a rolling update, two versions run at the same time. This means the database schema and message formats must be understood by both versions. If the new version added a column that the old one doesn't know about — that's not a problem; if the old version breaks when it sees the new data — that is a problem.
Rolling update is a sensible default for most changes.
Blue-green — an instant switch
Problem: a rollback takes minutes, but some errors are critical and every second counts.
Solution: keep two full environments. One (green) carries production traffic, while the new version is deployed in the other (blue). When blue is ready and verified, traffic is switched over entirely. Rollback: switch traffic back to green. It takes seconds.
What blue-green gives you:
- the new version can be verified in a production environment before users see it;
- rollback is a switch, not a new deploy.
What you pay for it:
- double the capacity during the rollout;
- the database is shared between both environments, so schema migrations must be understood by both versions;
- background processors (workers, queue consumers) cannot run in two versions at once — you switch them over, you don't duplicate them.
Blue-green is justified when the cost of an error is high and releases are rare: payment systems, critical APIs, systems with an SLA.
Canary — roll out to a percentage
Problem: even well-tested code can behave differently under real traffic and real data.
Solution: the new version first receives a small share of traffic — for example, 1%. If the metrics (errors, response time, business indicators) don't get worse, the share grows: 1% → 5% → 25% → 100%. On degradation — an automatic rollback. A hundredth of your users saw the problem, not everyone.
The name comes from the miners' practice of taking a canary into the mine: if the bird dies, the miners leave. The new version is the canary; if it "dies" on a small percentage, the rest of the users are safe.
What canary needs:
- infrastructure for weighted routing (service mesh, Argo Rollouts, Flagger);
- reliable metrics — without them canary turns into rolling with extra steps; promotion through the steps must follow formal criteria, not "let's eyeball it";
- time — the rollout stretches over hours.
Canary is justified with high traffic (at 100 requests per second, 1% is one request; there's no statistics) and mature observability.
Feature flags — deploy and release as separate events
Problem: sometimes the code is ready, but turning it on for everyone is scary. Or you need to test a feature on 10% of users without changing the infrastructure.
Solution: code travels separately from its activation. The code ships to production turned off and is turned on later — without a deploy. A flag can be turned on for everyone, for a percentage, or for a specific segment (for example, only beta users).
The logic is the same in every language: take an identifier (of a user, an order), ask the flag service, choose the path:
// Java — Unleash
UnleashContext ctx = UnleashContext.builder()
.userId(customerId)
.build();
if (unleash.isEnabled("new-pricing", ctx)) {
return newPricingPolicy.calculate(order);
}
return currentPricingPolicy.calculate(order);
// Go — Unleash
ctx := unleash.Context{UserId: customerId}
if unleash.IsEnabled("new-pricing", unleash.WithContext(ctx)) {
return newPricingPolicy.Calculate(order)
}
return currentPricingPolicy.Calculate(order)
// Node/TypeScript — Unleash
const enabled = isEnabled("new-pricing", { userId: customerId });
if (enabled) {
return newPricingPolicy.calculate(order);
}
return currentPricingPolicy.calculate(order);
# Python — Unleash
if client.is_enabled("new-pricing", {"userId": customer_id}):
return new_pricing_policy.calculate(order)
return current_pricing_policy.calculate(order)
What flags change in the process:
- the deploy becomes boring — code that's turned off carries no risk;
- turning on is reversible in seconds;
- gradual rollout across users without any weighted-routing infrastructure.
The main trap is eternal flags. A flag with no removal date becomes, a year later, a part of the architecture that nobody understands. Set a rule: every flag has an owner and a date by which it should disappear. After a full rollout — delete it.
You don't need a dedicated service to start: a table in PostgreSQL with a cache is quite enough. Unleash, LaunchDarkly and other solutions are needed when the table starts to fall short.
Comparing the strategies
| Rolling | Blue-green | Canary | + Feature flags | |
|---|---|---|---|---|
| Blast radius | Everyone, gradually | Everyone, instantly | Small percentage | Controlled segment |
| Rollback speed | Minutes | Seconds | Automatic | Seconds |
| Extra cost | None | Double capacity | Routing + metrics | Flag discipline |
| When to use | Default | Expensive error | High traffic, mature metrics | Always useful |
A practical combination for most teams: rolling + feature flags. Canary — when traffic and metrics have matured. Blue-green — selectively, for the most critical systems.
Common mistakes
Canary without formal criteria. "Let's watch Grafana" is not canary, it's hope. If there are no thresholds for automatic promotion or rollback, canary doesn't work as protection.
Blue-green with an incompatible schema migration. You switched to blue, migrated the schema — the old green no longer works with the new schema. A rollback in seconds turns into a restore from backup.
Eternal flags. A hundred flags tangled together by conditions are harder to reason about than any monolith. Flags are a temporary tool.
Workers under canary. A queue consumer in two versions that process the same data differently means desync. For background processors you use Recreate or a switch, not a split by percentage.
In short
- Release strategies differ in blast radius and rollback speed.
- Rolling — replicas are replaced one at a time; rollback takes minutes; the default choice.
- Blue-green — two environments, traffic switches over entirely; rollback in seconds; the price is double capacity.
- Canary — the new version gets a small percentage of traffic and grows by metrics; requires routing and reliable metrics.
- Feature flags — deploy and turning a feature on are separated; flags turn things on/off in seconds without a deploy.
- With rolling and canary, two versions run at the same time — the database schema and contracts must be understood by both.
- Flags are a temporary tool: create them with a removal date and remove them after the rollout.
What to read next
- Deploying to Kubernetes — the mechanics of a rolling update and compatible migrations.
- Pipeline principles — deploy and release as separate events.
- Branches and the release cycle — how code makes its way to these strategies.