Overview
Every time you ship code you have to answer one question: how do the new version
and the old version hand off? Do all users move at once, or a few at a time? Is
there downtime? Can you roll back fast if something breaks?
Deployment strategies are the different answers to that question. Each one trades
simplicity for some mix of zero downtime, safer rollout, and better
testing, and each extra guarantee costs you complexity and infrastructure.
This article walks through the main strategies from the simplest to the most
advanced, then ends with a simple flow to pick one: you start at big-bang and
"buy" each new capability with a bit more complexity.
How to decouple release and deploy
Traditionally people mix release and deploy and treat them as one single event:
you freeze the code in a branch for tests and validation, then put it into
production available for all clients at once.
Decoupling them means splitting that single event into two separate things:
- Deploy = publishing new code to production infrastructure.
- Release = exposing that code to clients, whenever you decide.
We can achieve that by using feature flags, a technique that can be combined
with any of the deployment strategies below. A feature flag is a runtime switch (a
config value, not a code change) that decides whether a piece of code actually
runs. You wrap the new behavior in a flag that defaults to "off" and ship the
code, so it sits in production deployed but not released. Nobody sees it until you
flip the flag on, and flipping it off brings the old behavior back instantly (a
"kill switch").
Image from CircleCI.
Trade-offs:
- Flag debt: flags pile up and need discipline to clean up once a feature is fully released. A good practice is to set a deadline for removing each flag so they don't live in the code forever.
- Extra complexity in the code, since both the old and new paths live side by side while the flag exists.
Problems it solves:
- A deploy stops being risky, because new code can go to production without being exposed to anyone.
- Rollback is instant: flip the flag off instead of redeploying the old version. This gives us space to be more innovative and less busy putting out fires like firemen.
- You can release to whoever you want first (1%, internal users, a single client) and widen from there.
- The release decision no longer depends on a deploy, so product or ops can pick the timing instead of waiting on a new build.
Big-bang
Stop all instances of the current version, then start the new one. No traffic
splitting, no coexistence, every user moves at once.
It is simple and fast to implement, which is its whole appeal. The cost is
downtime during the swap (seconds to minutes), high risk, and no easy
rollback: if the new version is broken, every user hits the problem immediately.
Good for dev/staging, scheduled maintenance windows, and breaking changes where
running two versions at once would be a problem anyway. AWS calls this same idea
all-at-once.
Recreate
Terminate the previous version completely, then rebuild the whole environment
with the new one. Only one version ever runs.
This looks almost identical to big-bang. Both have downtime, both move everyone
at once, neither runs two versions together. The difference is emphasis:
- Big-bang is about rollout scope: the change goes to everyone at once, with no phasing. It can happen in place on the same machine, so you stop the old process, swap the code, and start the new process.
- Recreate is about the infrastructure: destroy the old environment and rebuild it from a clean image, then deploy onto it.
Why throw the whole box away? Restarting in place carries over old state (stale
files, cached artifacts, manual tweaks), which drifts your config over time.
Recreate guarantees a clean slate every deploy. It is simple and predictable, but
still has downtime and limited testing before the switch.
Rolling
Update instances in batches while the app stays up, so old and new versions
serve traffic at the same time until every instance is on the new version. No
downtime, and you reuse the existing capacity, so no new infrastructure.
The catch is that both versions run at once, so the new version must be
backward-compatible with the old (same database, same API contracts).
Rollback means rolling backward through the batches.
The diagram above (and what I'm describing here) is rolling instance-based,
without any traffic-splitting logic: you just swap whole instances one batch at a
time. But there are two common ways to do the rollout:
- Instance-based (the traditional): swap whole instances one batch at a time. Simple, since you just need enough instances to roll through, and no logic to split requests. Each instance either runs the old version or the new one, and you accept a small dip in capacity during the rollout.
- Traffic-based: keep both versions up and use a load balancer to decide which instance each request hits, shifting the percentage of traffic toward the new one, often in equal increments on a timer (AWS calls this linear). This needs a load balancer or proxy that can split requests, but it handles the transition more smoothly.
Rolling is a good default for backward-compatible changes when infra cost matters
and you release often.
Canary
Route a small slice of production traffic (typically 1% to 5%) to the new
version while everyone else stays on the current one. Watch the metrics; if they
stay healthy, grow the slice until the new version takes over.
Image from CircleCI.
Compared to rolling, canary adds real-time monitoring and automated analysis:
you are not just shifting traffic, you are watching a small blast radius for
errors and rolling back before most users ever see them. That buys minimal
production risk, early bug detection, and real feedback from real users, at a much
lower cost than a full duplicate environment.
The key difference from rolling is the rollback. Canary watches metrics and,
if they degrade, sends all traffic back to the current version. Rolling has no
metrics gate, so to undo it you roll backward through the batches, which is
slower and manual.
The price is the machinery: traffic splitting, monitoring, and enough traffic for
the metrics to mean something. Best for high-traffic services that already have
monitoring in place.
Blue/green
Keep two identical production environments. One (blue) serves all traffic;
the other (green) sits idle. Deploy the new version to green, validate it fully,
then switch all traffic over at once. If something breaks, switch back.
The win is rollback speed (under a minute) and full validation of the real
environment before any user touches it, with no downtime and a seamless switch.
The cost is double compute: you pay for two complete environments. A shared
database still needs care (an expand-migrate-contract approach), since both
versions may touch it around the switch. AWS notes this is sometimes called
red/black.
Reach for it when rollback speed is critical and the budget allows the
duplication.
Shadow
Run the new version alongside the current one and mirror real production
traffic to it, but never serve its responses to users. The shadow's output is
logged and compared, then discarded.
Image from CircleCI.
This is the safest way to test, because users are never affected while you put the
new version under real production load and uncover real-world issues. The catch is
stateful operations: database writes, payments, emails, anything with side
effects runs twice unless you filter it out. You also pay for a duplicate
environment plus the request-duplication plumbing.
Great for model swaps, algorithm rewrites, and validating database migrations
under real load when you cannot tolerate downtime. Feature flags cannot replicate
this, which is what makes it unique.
Pros and cons
| Strategy | Pros | Cons |
|---|---|---|
| Big-bang | Simple and fast; fine for breaking changes (no coexistence) | Downtime; high risk; no easy rollback; every user hit immediately; new version isn't tested under real traffic before going live |
| Recreate | Simple and predictable; clean slate, single running version | Downtime; high risk; no easy rollback; every user hit immediately; new version isn't tested under real traffic before going live |
| Rolling | Reuses existing capacity (no new infra); no downtime; issues hit only a subset; old version still there to roll back to | Needs backward compatibility; rollback means rolling in reverse; traffic-based variant needs a load balancer; more to manage |
| Canary | Cheaper than blue/green; minimal production risk; early bug detection; real user feedback; easy rollback | Needs traffic splitting, monitoring, and automated analysis; slower to full release; needs enough traffic for metrics |
| Blue/green | No downtime; fast rollback (under a minute); full validation before the switch | Double compute cost; shared database still needs expand-migrate-contract |
| Shadow | Safest, since users are never affected; full production-load testing; finds real-world issues | Stateful ops run twice and must be filtered; duplicate environment plus request duplication |
| Feature flags (on top of any of the above) | Decouples deploy from release; instant kill-switch rollback; no extra infra | Flag debt; needs cleanup discipline; cannot replicate shadow |
How to choose
Think of it as a ladder. You start simple and only climb when you need the next
capability, and each step costs more complexity and infrastructure than the last.
As pseudocode, cheapest first, adding one guarantee at every step:
// start simple; each need bumps you up one step (more complexity, more advantage)
pick = Big-bang / Recreate // simplest: accepts downtime, risky
if (needZeroDowntime)
pick = Rolling (instance-based) // + no downtime
if (needSmootherTransition)
pick = Rolling (traffic-based) // + shift by % of traffic
if (needMonitoringOnSmallSlice)
pick = Canary // + early detection, auto rollback
if (needFasterRollback)
pick = Blue/green // + instant switch and rollback
if (needRealLoadTestingWithNoUserRisk)
pick = Shadow // + test on real traffic, zero user impact
- Can you accept downtime, and do you ship breaking changes? Stop here, at big-bang / recreate. No coexistence means breaking changes are fine, and there is nothing to configure.
- Want zero downtime, but no duplicate environment and no traffic-splitting logic? Climb to rolling (instance-based). You roll batches of instances and accept a small capacity dip.
- Want that transition to be smoother with traffic-splitting logic? Move to rolling (traffic-based / linear), shifting traffic by percentage instead of by whole instances.
- Want to catch problems on a small slice with real-time monitoring? Step up to canary: expose 1% to 5%, watch the metrics, and roll back before most users are affected.
- Want the fastest possible rollback and can pay for a duplicate environment? Go to blue/green: validate a full second environment, then flip.
- Have a duplicate environment and need real production-load testing with no user risk? Top of the ladder is shadow: mirror real traffic, serve none of it.
Each step trades a bit more complexity for one more guarantee: zero downtime, then
smoother traffic, then early detection, then instant rollback, then risk-free
testing. Pick the lowest step that gives you what you actually need.
Read also
Jenkins CI/CD from scratch,
so you can learn how to implement these deployment strategies with the help of a
tool that lets you automate deploys and rollbacks easily, creating pipelines with
build, testing, validation and more.








Top comments (0)