Blue/Green & Canary
Blue/green deploys stand up a full duplicate of production (the "green" stack), warm it, switch the load balancer to it, and keep the old stack ("blue") on standby. Rollback is a single LB swap. Cost: you pay for double capacity briefly. Win: the safest large-change deploy short of feature flags, especially good for schema-compatible releases.
GitHub Actions blue/green to AWS via target groups
EXAMPLE
# .github/workflows/blue-green.yml
name: blue-green
on:
workflow_dispatch:
inputs:
image:
description: 'Image tag to deploy (e.g. v1.4.0)'
required: true
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-southeast-2
ALB_LISTENER: arn:aws:elasticloadbalancing:ap-southeast-2:123456789012:listener/app/shop-prod/abc/def
TG_BLUE: arn:aws:elasticloadbalancing:ap-southeast-2:123456789012:targetgroup/shop-blue/aaa
TG_GREEN: arn:aws:elasticloadbalancing:ap-southeast-2:123456789012:targetgroup/shop-green/bbb
CLUSTER: shop-prod
SERVICE_GREEN: shop-green
jobs:
deploy-and-warm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with: { role-to-assume: ${{ secrets.DEPLOY_ROLE_ARN }}, aws-region: ${{ env.AWS_REGION }} }
# 1) Deploy the new image to the GREEN stack
- name: Update GREEN service
run: |
aws ecs update-service --cluster "${CLUSTER}" --service "${SERVICE_GREEN}" \
--task-definition shop-app:${{ github.event.inputs.image }} --force-new-deployment
aws ecs wait services-stable --cluster "${CLUSTER}" --services "${SERVICE_GREEN}"
# 2) Warm GREEN behind a private TG so the JIT, connection pools, and
# autoscaling are ready BEFORE customer traffic arrives.
- name: Warm GREEN
run: scripts/warm-tg.sh "${TG_GREEN}" 5m
# 3) Health probe — same metrics we will use for rollback
- name: Smoke + health
run: scripts/check-health.sh --tg "${TG_GREEN}" --window 3m \
--error-rate-max 1 --p95-ms-max 800
# 4) Switch the ALB listener default rule from BLUE -> GREEN.
# This is the atomic cut-over.
- name: Flip traffic to GREEN
run: |
aws elbv2 modify-listener --listener-arn "${ALB_LISTENER}" \
--default-actions Type=forward,TargetGroupArn=${TG_GREEN}
# 5) Watch for 10 minutes. Auto-rollback if metrics breach.
- name: Bake
id: bake
run: scripts/check-health.sh --tg "${TG_GREEN}" --window 10m \
--error-rate-max 1 --p95-ms-max 800
# 6) Auto-rollback: flip the listener back to BLUE
- name: Roll back to BLUE
if: failure()
run: |
aws elbv2 modify-listener --listener-arn "${ALB_LISTENER}" \
--default-actions Type=forward,TargetGroupArn=${TG_BLUE}
scripts/notify.sh slack --channel deploys --status failure
exit 1
# 7) Final step: scale BLUE down so we are not paying for it forever
- name: Scale BLUE down (after soak)
if: success()
run: aws ecs update-service --cluster "${CLUSTER}" --service shop-blue --desired-count 0
# Notes for the operator
# - Blue + Green target groups MUST share the same health-check path & port.
# - Schema migrations must be SAFE to run before the cut-over: additive only.
# For destructive changes, do a two-phase migration over multiple releases.
# - Keep BLUE warm for at least the soak window. Cold rollback is sometimes worse than no rollback.
Why it matters
Blue/green is safest when migrations are additive: add columns, ship new code that reads both, deploy green, switch, then drop the old column in a later release. A drop-and-redeploy in one go forces you to roll the DB forward AND back, which negates the simple LB-flip rollback that makes blue/green attractive.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# Blue-green: run 2 environments, swap traffic. # Canary: send 5% → 25% → 100%; watch metrics; auto-rollback on error budget.Try it Yourself »
Discussion
Loading…