April 26, 2025

A Practical Guide to Canary Deployment in DevOps – 2025

Canary Deployment Architecture with DevOps Tools

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 and canary


    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

    canary-deployment/
    ├── app-stable/
    │ └── index.html
    ├── app-canary/
    │ └── index.html
    ├── nginx/
    │ └── default.conf
    ├── docker-compose.yml
    └── README.md
    
    1️⃣ Create Sample Apps
    🔹 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

  1. Start with a 90/10 split

  2. Update weight in nginx/default.conf to increase Canary gradually

  3. 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

  1. Start with a defined traffic split (e.g., 90/10)

  2. Gradually shift traffic to the canary version (e.g., 80/20 → 70/30 → 50/50 → 0/100)

  3. Monitor responses or health checks

  4. 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
Amritpal

I’m the owner of “DevOpsTechy.online” and been in the industry for almost 6+ years. What I’ve noticed particularly about the industry is that it reacts slowly to the rapidly changing world of technology. I’ve done my best to introduce new technology into the community with the hopes that more technology can be utilized to serve our customers. I’m going to educate and at times demonstrate that technology can help businesses innovate and thrive. Throwing in a little bit of fun and entertainment couldn’t hurt right?

Amritpal

I’m the owner of “DevOpsTechy.online” and been in the industry for almost 6+ years. What I’ve noticed particularly about the industry is that it reacts slowly to the rapidly changing world of technology. I’ve done my best to introduce new technology into the community with the hopes that more technology can be utilized to serve our customers. I’m going to educate and at times demonstrate that technology can help businesses innovate and thrive. Throwing in a little bit of fun and entertainment couldn’t hurt right?

View all posts by Amritpal →

Leave a Reply

Your email address will not be published. Required fields are marked *