14.3 Deployment Patterns
A focused guide to Deployment Patterns, connecting core concepts with practical Docker and container operations.
Deployment patterns for Docker in production are the established strategies for replacing a running version of a containerized service with a new one, each making a different trade-off between deployment speed, infrastructure cost, and the blast radius of a bad release.
Recreate
The simplest pattern stops the old container entirely before starting the new one, accepting a brief gap in availability between the two:
docker stop my-api
docker rm my-api
docker run -d --name my-api my-api:1.5.0
This is acceptable for internal tools or services with tolerant clients, but for anything user-facing with availability expectations, the downtime window, however brief, is usually unacceptable.
Rolling update
A rolling update replaces replicas of a service incrementally, a few at a time, so that some capacity remains available throughout the deployment:
services:
api:
image: my-api:1.5.0
deploy:
replicas: 6
update_config:
parallelism: 2
delay: 10s
order: start-first
docker service update --image my-api:1.5.0 my-api
order: start-first starts new replicas before stopping old ones, keeping total capacity steady throughout the rollout rather than briefly dipping as old replicas are removed before their replacements are ready.
Blue-green deployment
Blue-green deployment runs two complete, independent environments, only one of which receives live traffic at a time, and switches traffic from the old environment to the new one only after the new one is fully validated:
docker compose -f docker-compose.green.yml up -d
curl http://green.internal:3000/healthz
upstream backend {
server green.internal:3000;
}
Switching the load balancer or reverse proxy configuration to point at the new environment is effectively instantaneous, and rolling back is just as fast, since the previous environment (blue) is left running, untouched, and ready to receive traffic again if the new one shows problems.
upstream backend {
server blue.internal:3000;
}
The cost of this pattern is running two full environments simultaneously during the transition, which roughly doubles the infrastructure footprint for the duration of each deployment.
Canary deployment
A canary deployment routes a small percentage of live traffic to the new version while the majority continues to the stable version, observing the canary's behavior under real traffic before expanding it to handle everything:
services:
api-stable:
image: my-api:1.4.0
deploy:
replicas: 9
api-canary:
image: my-api:1.5.0
deploy:
replicas: 1
upstream backend {
server stable-1:3000 weight=9;
server canary-1:3000 weight=1;
}
If metrics from the canary (error rate, latency, business-specific signals) remain healthy over an observation window, the new version's weight is increased incrementally until it serves all traffic; if not, the canary is removed with minimal impact, since only a small fraction of traffic was ever exposed to the problem.
Shadow deployment
A shadow deployment sends a copy of live traffic to the new version without that traffic's response ever being returned to the real client, validating the new version's behavior against real-world inputs with zero risk to actual users:
location / {
mirror /shadow;
proxy_pass http://stable-backend;
}
location /shadow {
internal;
proxy_pass http://canary-backend;
}
This pattern is particularly useful for validating performance characteristics or detecting unexpected errors in a new version before it serves any traffic that matters, though it requires the new version's side effects (writes to a database or external API) to be carefully isolated or mocked, since shadow traffic should not produce real, duplicated effects.
A/B and feature-flag-driven rollout
Rather than deploying entirely new containers to control rollout, some teams deploy the new code to all replicas but gate the new behavior behind a feature flag, controlling exposure through configuration rather than through which containers are running:
if (featureFlags.isEnabled('new-checkout-flow', userId)) {
return renderNewCheckout();
}
return renderLegacyCheckout();
This decouples deployment (getting new code running) from release (exposing new behavior to users), which allows much finer-grained and more easily reversible control over exposure than container-level routing alone provides, at the cost of carrying both code paths in the same deployed artifact until the flag is fully rolled out and removed.
Choosing a pattern based on risk tolerance
The right pattern for a given service depends on its tolerance for brief downtime, the cost of running duplicate infrastructure, and how quickly a bad release needs to be detected and reverted:
Recreate → lowest cost, accepts downtime
Rolling update → no downtime, moderate cost, moderate blast radius
Blue-green → no downtime, highest cost, fastest rollback
Canary → no downtime, low cost, smallest blast radius, slower full rollout
Shadow → zero risk to real users, highest implementation complexity
Many production systems combine these: a canary phase to validate a release under limited real traffic, followed by a rolling update to complete the rollout once the canary has proven healthy.
Rollback as part of every pattern
Every deployment pattern should have a defined, tested rollback path that does not depend on the original deployment having gone smoothly:
docker service update --rollback my-api
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --no-deps my-api
A rollback path that has only ever been used successfully in a calm, low-pressure test is not the same as one proven to work quickly during an actual incident; periodically exercising the rollback path, the same way a backup restore is periodically tested, keeps confidence in it accurate.
Common mistakes
- Choosing recreate-style deployment for a user-facing service without acknowledging the downtime it introduces on every release.
- Running a rolling update with
order: stop-firstfor a service with limited replicas, briefly reducing capacity below what live traffic requires. - Treating a canary's healthy metrics over a too-short observation window as sufficient evidence to proceed, missing issues that only manifest under sustained or peak load.
- Implementing blue-green deployment without verifying the new environment is fully healthy before switching traffic, effectively turning it into a recreate deployment with extra infrastructure cost.
- Never testing the rollback path until an actual incident forces the first real attempt at using it.
The right deployment pattern is chosen deliberately based on a service's actual availability requirements and acceptable risk, not adopted by default, and whichever pattern is chosen should include a rollback path that has been exercised under realistic conditions before it is needed for real.