Introduction
Setting up Canary Deployment with docker-compose
is a great way to test new application versions with a small subset of traffic before rolling out the change to everyone. Let’s walk through a practical setup using:
-
Docker Compose
-
Nginx as a reverse proxy
-
Two versions of the same app:
stable
andcanary
What is Canary Deployment?
Canary deployment releases the new version of your app to a small percentage of users (e.g. 10%) while the rest continue to use the stable version. Once it’s tested and verified, you gradually shift more traffic.
Project Structure
🔹 app-stable/index.html
<!DOCTYPE html>
<html>
<head><title>Stable</title></head>
<body>
<h1>Welcome to STABLE version</h1>
</body>
</html>
🔹 app-canary/index.html
<!DOCTYPE html> <html> <head><title>Canary</title></head> <body> <h1>Welcome to CANARY version</h1> </body> </html>
2️⃣ Nginx Config for Weighted Routing 🔹 nginx/default.conf
upstream app_backend { server app-stable:80 weight=90; server app-canary:80 weight=10; } server { listen 80; location / { proxy_pass http://app_backend; } }
Here, 90%
of the traffic goes to the stable app, and 10%
to the canary app. 3️⃣ Docker Compose Setup
🔹 docker-compose.yml
version: '3.8'
services:
app-stable:
image: nginx:alpine
volumes:
- ./app-stable:/usr/share/nginx/html
networks:
- canary-net
app-canary:
image: nginx:alpine
volumes:
- ./app-canary:/usr/share/nginx/html
networks:
- canary-net
nginx:
image: nginx:alpine
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
ports:
- "8080:80"
depends_on:
- app-stable
- app-canary
networks:
- canary-net
networks:
canary-net:
4️⃣ Start Your Environment
docker-compose up -d
Access your app at: http://localhost:8080
Keep refreshing — you’ll mostly see Stable
, but occasionally Canary
.
Test It Out
To visually verify canary routing:
curl -s http://localhost:8080 | grep h1
Run it 20+ times and count how many are Canary vs Stable.
Rollout Strategy
-
Start with a 90/10 split
-
Update
weight
innginx/default.conf
to increase Canary gradually -
Reload Nginx:
docker-compose exec nginx nginx -s reload
Once canary is tested, swap the roles or promote Canary as Stable.
Automating Canary Deployment with Docker Compose involves scripting the traffic split updates and service promotion. Here’s a complete approach to automate this process:
Learn about Blue/Green deployment. Click here
Goals of Automation
-
Start with a defined traffic split (e.g., 90/10)
-
Gradually shift traffic to the canary version (e.g., 80/20 → 70/30 → 50/50 → 0/100)
-
Monitor responses or health checks
-
Promote Canary to Stable automatically if healthy
Step-by-Step Canary Automation
Get full code from Github
📁 Directory Structure
canary-deployment/ ├── app-canary │ └── index.html ├── app-stable │ └── index.html ├── auto_update_weights.sh ├── docker-compose.yml ├── monitor.sh ├── nginx │ ├── default.conf │ └── default.template.conf └── update_weights.sh
1. Nginx Config Template
nginx/default.template.conf
upstream app_backend { server app-stable:80 weight={{STABLE_WEIGHT}}; server app-canary:80 weight={{CANARY_WEIGHT}}; } server { listen 80; location / { proxy_pass http://app_backend; } }
2. Bash Script:
update_weights.sh
#!/bin/bash # Usage: ./update_weights.sh <stable_weight> <canary_weight> STABLE_WEIGHT=$1 CANARY_WEIGHT=$2 echo "🌀 Updating traffic split: Stable=$STABLE_WEIGHT%, Canary=$CANARY_WEIGHT%" # Replace placeholders sed -e "s/{{STABLE_WEIGHT}}/$STABLE_WEIGHT/" \ -e "s/{{CANARY_WEIGHT}}/$CANARY_WEIGHT/" \ nginx/default.template.conf > nginx/default.conf # Reload nginx inside container docker-compose exec nginx nginx -s reload echo "Weights updated successfully."
3. Automate Gradual Promotion (Optional)
#!/bin/bash for split in 80:20 60:40 50:50 30:70 10:90 0:100; do STABLE=$(echo $split | cut -d: -f1) CANARY=$(echo $split | cut -d: -f2) ./update_weights.sh $STABLE $CANARY echo "⏳ Waiting to observe traffic behavior..." sleep 30 # or longer for real-world tests done echo "🎉 Canary promoted successfully!"
Optional: Monitor Health (Simple)
Add curl-based check to only proceed if Canary responds properly:
CANARY_RESP=$(curl -s http://localhost:8080 | grep CANARY) if [[ -z "$CANARY_RESP" ]]; then echo "❌ Canary not responding. Rolling back..." ./update_weights.sh 100 0 exit 1 else echo "✅Canary healthy." fi
Final Workflow
Follow me on Linkedin
- Launch everything:
docker-compose up -d
2. Start canary rollout:
./update_weights.sh 90 10
3. Automate progressive rollout.
auto_update_weights.sh