Canary Deployment: Progressive Delivery with Feature Flags

Loading...·13 min read

A canary deployment releases a change to a small slice of production traffic first — often 1% of users — watches how it behaves against real requests, and only then widens exposure. The name comes from the "canary in a coal mine": a small, early warning system that fails before the whole system does. Done well, a canary deployment turns a risky big-bang release into a controlled, reversible experiment where the worst-case blast radius is a rounding error of your user base instead of everyone at once.

Feature flags are what make this practical without re-deploying code at every step. Instead of shipping a new binary to 1%, then 10%, then 50% of your servers, you deploy the code once with the new path gated behind a flag, then move a dial: 1% → 10% → 50% → 100%, with an instant rollback to 0% if a guardrail trips. This article explains canary deployments and the broader practice of progressive delivery, compares canary against blue-green, rolling, and dark launch strategies, and shows how to implement each stage in Optimizely Feature Experimentation using targeted deliveries, audiences, and Feature Rollouts.

What is a canary deployment?

A canary deployment is a release strategy where a new version of a feature is exposed to a small, controlled percentage of users before it reaches everyone. You monitor that cohort for errors, latency regressions, and business-metric harm. If the canary stays healthy, you progressively increase the percentage. If it does not, you roll back — ideally in seconds, affecting only the users who were in the canary group.

The defining characteristics of a canary deployment are:

  • A small initial exposure. The canary group is a minority of traffic (commonly 1–5%) so that a bad change hurts few people.

  • Real production traffic. Unlike staging, the canary sees genuine users, genuine data, and genuine load — the conditions where most incidents actually surface.

  • Progressive ramp. Exposure increases in stages, each gated on the canary group looking healthy.

  • Fast rollback. Reverting is a configuration change, not a re-deploy, so mean-time-to-recovery is measured in seconds.

Canary deployment is one technique inside the wider discipline of progressive delivery: decoupling deploy (shipping code to production) from release (exposing it to users), and controlling release along axes of percentage, audience, and time.

Canary release vs. blue-green vs. rolling vs. dark launch

"Canary release" and "canary deployment" are used interchangeably. It helps to place the canary alongside the other common release strategies, because they solve overlapping but distinct problems. Many mature teams combine them — for example, a dark launch to validate wiring, followed by a canary release to validate impact.

Strategy

How it works

Initial blast radius

Rollback speed

Measures user impact?

Canary release

Expose the new version to a small % of production traffic, then ramp up in stages

Very small (1–5%)

Instant (change the % to 0)

Yes — you watch the canary cohort

Blue-green

Run two identical environments; switch all traffic from old (blue) to new (green) at once

All users at cutover

Fast (switch back to blue)

No — it is all-or-nothing

Rolling

Replace instances of the old version with the new one, a few at a time

Grows as instances are replaced

Moderate (roll instances back)

Not directly

Dark launch

Deploy and enable the code path but keep it invisible to users (or only shadow-run it)

None (no user sees it)

Instant (disable the path)

No — validates plumbing, not reception

The key trade-offs:

  • Blue-green minimizes downtime and gives a clean rollback, but at the switch every user moves at once, so a latent bug hits 100% of traffic instantly. Canary trades a slightly more complex rollout for a dramatically smaller blast radius.

  • Rolling deployments are the default in most orchestrators (for example, Kubernetes). They limit how many instances change at once, but they control infrastructure exposure, not user exposure, and they do not let you hold a stable percentage while you measure.

  • Dark launch is about deploying without releasing — the code is in production but dormant. It is the perfect precursor to a canary: ship it dark, confirm it does not break startup or increase resource usage, then begin the canary ramp.

With feature flags, you do not have to pick one strategy forever. The same flag can host a dark launch (flag on only for internal users), then a canary (flag on for 1% of everyone), then a full rollout — without a single additional deploy.

How feature flags enable progressive delivery

The traditional problem with canary deployments is that they require infrastructure to split traffic between two versions of your code. Feature flags collapse that requirement. You deploy one version of the application that contains both the old and the new code path, guarded by a flag decision. A flag-management service then decides, per user, which path runs — and you change that decision remotely, without touching the deployment.

That decoupling is what makes progressive delivery smooth: the ramp is a series of configuration changes, each reversible, each scoped to a percentage or an audience.

flowchart TD
  A[Deploy code, flag OFF] --> B[Dark launch: on for internal users]
  B --> C[Canary: 1% of production traffic]
  C --> D{Guardrails healthy?}
  D -->|Yes| E[Ramp to 10%]
  E --> F{Healthy?}
  F -->|Yes| G[Ramp to 50%]
  G --> H{Healthy?}
  H -->|Yes| I[100% full release]
  D -->|No| R[Roll back: set flag to 0%]
  F -->|No| R
  H -->|No| R
  R --> C

Because the flag decision is evaluated in your application at request time, the same mechanism that powers the canary can later power an A/B test on the winning variation, or a permanent kill switch. For the wider set of conventions that keep this manageable at scale — naming, cleanup, and ownership — see feature flag best practices.

The canary deployment workflow, step by step

A disciplined canary rollout follows the same sequence whether you deploy weekly or continuously.

1. Deploy the code with the flag off

Ship the new code path to production behind a flag that is off for everyone. Nothing changes for users. This is the safest possible state and confirms the deploy itself is clean.

2. Dark launch to internal users

Turn the flag on for an internal audience — employees, a QA team, or a beta cohort identified by a user attribute. You are validating that the feature works end to end against production data before any external user sees it.

3. Start the canary at 1%

Open the flag to a small percentage of production traffic. Bucketing must be sticky: a user who enters the canary should stay in it on subsequent visits, so behavior is consistent and your metrics are clean. Watch error rates, latency, and the guardrail metrics you defined for this release.

4. Ramp in stages

If the canary is healthy, increase exposure in deliberate steps — a common ladder is 1% → 10% → 50% → 100%. Hold at each stage long enough to gather a meaningful sample. Ramping too fast defeats the purpose; ramping too slowly wastes time. Let the risk of the change set the pace: schema migrations and payment paths deserve a slower ladder than a copy change.

5. Reach full release

Once 100% of traffic runs the new path and stays healthy, the canary is complete. Leave the flag in place as a kill switch for a cooling-off period, then remove the old code path and retire the flag so it does not become permanent technical debt.

6. Roll back instantly if a guardrail trips

At any stage, if error rates spike or a guardrail metric degrades, set the flag back to 0%. Because rollback is a configuration change rather than a re-deploy, recovery is near-instant and affects only the users who were in the canary at that moment.

Dark launch: decoupling deploy from release

A dark launch deserves its own note because it is the most under-used stage. Dark launching means the new code is live in production but hidden from users — either fully off, visible only to an internal audience, or "shadow" running (executing the new path and comparing its output without showing it to the user).

Dark launches answer questions a canary cannot answer safely:

  • Does the new code path start up, allocate connections, and warm caches without regressing resource usage?

  • Does a new backend dependency hold up under production request shapes and volume?

  • Do the new queries or writes behave against real data before any user depends on the result?

Because a dark launch exposes the change to zero external users, its blast radius is nil, and disabling it is instant. Treat it as the on-ramp to the canary: dark launch validates the plumbing, the canary validates the reception.

Implementing canary deployments in Optimizely Feature Experimentation

Optimizely Feature Experimentation implements every stage above with flag rules. A flag's ruleset is a prioritized list of rules; each rule describes what variation to deliver, to whom, and to what percentage of traffic. Rules are evaluated in order — experiments first, then flag deliveries, then the final "Then, for everyone else" rule — and evaluation stops as soon as a user qualifies for a rule. (Interactions between flag rules)

Targeted delivery for the percentage ramp

A targeted delivery (also called a flag delivery) is the rule type built for canary rollouts. Per Optimizely's documentation, a targeted delivery lets you "control who sees your flag," "gradually increase or roll out the flag to more users," and "roll back the flag if you encounter bugs or other problems." (Run flag deliveries)

You create a targeted delivery rule on the flag, leave the audience open (everyone) or scope it, and set the Traffic Allocation slider to your current canary percentage. Ramping the canary is simply moving that slider: 1%, then 10%, then 50%, then 100%. Rolling back is setting it to 0%. Targeted deliveries are free, do not consume monthly active users (MAU) or impressions, and carry negligible evaluation cost — but they provide no built-in analytics, so you monitor impact through your own observability stack.

In your application, the same flag decision drives which code path runs. The SDK's Decide method returns whether the flag is enabled for the current user:

// Node.js / JavaScript SDK
const user = optimizely.createUserContext(userId, userAttributes);
const decision = user.decide('checkout_redesign');

if (decision.enabled) {
  // New code path — only runs for users bucketed into the canary
  renderCheckoutRedesign();
} else {
  // Existing, stable path
  renderLegacyCheckout();
}

For server-side SDKs, configure a user profile service so bucketing is sticky across requests — otherwise a user could flip between the canary and the control on successive visits, contaminating both the experience and your metrics.

You can also drive the ramp through the REST API by patching the flag's ruleset, which is convenient for scripting a staged rollout. The rule's type is targeted_delivery, and percentage_included is expressed in basis points (10000 = 100%):

curl --request PATCH \
  --url 'https://api.optimizely.com/flags/v1/projects/PROJECT_ID/flags/checkout_redesign/environments/production/ruleset' \
  --header 'authorization: Bearer TOKEN' \
  --header 'content-type: application/json-patch+json' \
  --data '[
    {
      "op": "add",
      "path": "/rules/checkout_canary",
      "value": {
        "key": "checkout_canary",
        "name": "Checkout redesign canary",
        "type": "targeted_delivery",
        "variations": { "on": { "key": "on", "name": "on", "percentage_included": 100 } }
      }
    }
  ]'

Here percentage_included: 100 means 1% (100 basis points). Raise it to 1000 for 10%, 5000 for 50%, and 10000 for the full release. When you PATCH a ruleset, first GET the current ruleset and merge your change so you do not overwrite other rules.

Audiences for ring-based canaries

Percentage is only one axis. Optimizely audiences let you canary by who rather than how many — the ring-deployment pattern, where a change reaches concentric rings of increasing risk tolerance: internal staff → beta opt-ins → a low-risk region → everyone. Audiences are built from custom user attributes such as plan, device type, region, or application version, and can be reused across rules. (Target audiences)

For example, a dark-launch ring is a targeted delivery scoped to an is_internal audience at 100% traffic. When that ring is healthy, you add a broader targeted delivery with no audience at 1% to begin the public canary. A note on exclusion: audiences in Feature Experimentation include users rather than exclude them, so to keep a group out you define an advanced audience combination with not in Code Mode rather than setting their traffic to 0%.

Feature Rollouts for a measured canary

A plain targeted delivery has no analytics, so you rely on external monitoring to decide whether to ramp. Feature Rollouts (in beta at the time of writing) close that gap: they combine the simplicity of a targeted delivery with the measurement power of an A/B test, letting you release a single variation progressively — 10%, 25%, 50%, then 100% — while tracking impressions, conversions, and business metrics at each stage. (Run Feature Rollouts)

The trade-off is cost and plan: unlike targeted deliveries, Feature Rollouts fire impression events and therefore consume MAU and impressions, and they require a Growth or Enterprise plan. Choose a Feature Rollout when the release decision needs data attached to it — for example, when you are rolling out the winning variation from a concluded experiment and want to confirm the win holds under full traffic. If you already run experiments server-side, the same instrumentation applies; see server-side A/B testing with Optimizely for the SDK and event plumbing.

Rolling back

Rollback is the feature that justifies the whole approach. With any of the rule types above, reverting the canary is a single change: set the traffic allocation to 0% (or pause the flag's ruleset). Because the decision is evaluated at request time, the next request from every user in the canary immediately takes the stable path — no build, no deploy, no waiting for instances to cycle.

Guardrail metrics: knowing when to ramp or roll back

A canary is only as good as the signal that tells you it is healthy. Ramping on "it looks fine" is how latent regressions reach 100%. Define, before you start, the metrics that must stay within bounds at every stage — error rate, p95 latency, and the business counter-metrics that a purely technical dashboard misses, such as checkout completion or refund rate. These are your guardrail metrics: they catch the change that improves your primary metric while quietly harming something you care about more. A canary that raises conversion but doubles support tickets is not a success, and only a guardrail will tell you.

To keep a slice of users entirely outside all rollouts as a long-run baseline — so you can measure the cumulative effect of everything you ship — consider global holdouts, which reserve a percentage of users who never see new feature rollouts or experiments.

Common pitfalls

  • Ramping too fast. Jumping from 1% to 100% in an hour gives regressions no time to surface. Hold each stage long enough to collect a meaningful sample for your guardrails.

  • Non-sticky bucketing. Without a user profile service on server-side SDKs, users flip between canary and control across requests, producing inconsistent experiences and unusable metrics.

  • Watching only technical metrics. Error rate and latency miss business harm. Pair them with guardrail metrics that reflect user and revenue outcomes.

  • Leaving flags forever. A canary flag that never gets cleaned up becomes permanent complexity and a latent risk. Retire the flag and the old code path once the release is stable.

  • Confusing deploy with release. Treating a deploy as a release forfeits the whole benefit. Deploy dark, release progressively.

  • No rollback rehearsal. If you have never practiced setting the flag to 0%, you will hesitate during an incident. Make rollback a boring, well-worn action.

Frequently asked questions

What is the difference between a canary deployment and a blue-green deployment?

A canary deployment exposes the new version to a small percentage of production traffic and ramps up in stages, so a bad change affects only a fraction of users. A blue-green deployment runs two full environments and switches all traffic at once, so at cutover every user gets the new version simultaneously. Canary minimizes blast radius and lets you measure impact as you go; blue-green optimizes for a clean, instant cutover and rollback but is all-or-nothing.

How small should the initial canary be?

A common starting point is 1–5% of production traffic. The right number depends on your total traffic and the risk of the change: you want the canary large enough to produce a meaningful signal on your guardrail metrics within a reasonable window, but small enough that a serious regression harms few users. High-risk changes (payments, migrations) warrant the low end; low-risk changes can start higher.

Do I need a service mesh or special infrastructure for canary deployments?

No. Traffic-splitting meshes are one way to run canaries at the infrastructure layer, but feature flags achieve the same user-level control without them. You deploy one build containing both code paths and let the flag-management service decide who runs the new path, which also gives you per-user stickiness and instant rollback that infrastructure splitting alone does not.

What is the difference between a dark launch and a canary release?

A dark launch deploys and enables a code path but keeps it invisible to users — often on only for internal accounts or shadow-running — so its blast radius is zero and it validates that the plumbing works. A canary release exposes the change to a small slice of real users to validate reception and impact. Dark launch and canary are complementary stages: dark launch first, then canary.

How does Optimizely handle rollback during a canary?

Rollback is a configuration change, not a deploy. Set the targeted delivery's traffic allocation to 0% or pause the flag's ruleset, and because the flag decision is evaluated at request time, every user in the canary takes the stable path on their next request. There is no build or instance-cycling delay, so mean-time-to-recovery is effectively immediate.