mcpField
A
aws-ecs-codedeploy-blue-green

aws-ecs-codedeploy-blue-green

github.com/mizchiCloud & DevOps41640 installsUpdated 1w ago
Repository

About aws-ecs-codedeploy-blue-green

A reference guide to blue/green deployment patterns for AWS ECS using ALB weighted routing or CodeDeploy. It covers traffic shifts, infrastructure configuration, alarm-based rollback, and common configuration pitfalls.

Key features

  1. 01Shift traffic with ALB weights
  2. 02Configure CodeDeploy blue/green deployments
  3. 03Use CloudWatch alarms for rollback
  4. 04Highlight Terraform drift considerations

Use cases

  1. 01Canary rollout to an ECS target group
  2. 02Keeping an existing CodeDeploy pipeline
  3. 03Rolling back deployments when alarms fire

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/mizchi/skills --skill aws-ecs-codedeploy-blue-green

After installing, type/aws-ecs-codedeploy-blue-greenin Claude Code to confirm it loaded.

Bundled filesREADME.md

Details

From the project README

AWS ECS Blue/Green Deployments

Recommendation: ALB-Native Weighted Routing (Preferred)

CodeDeploy adds IAM roles, appspec.json wiring, and a separate control plane. For most ECS blue/green needs, ALB weighted target groups achieve the same result with less setup.

How it works

Define two target groups (blue/green) and a single listener rule that splits traffic by weight:

resource "aws_lb_target_group" "blue" {
  name        = "myapp-blue"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = var.vpc_id
  target_type = "ip"

  health_check {
    path                = "/health"
    interval            = 15
    healthy_threshold   = 2
    unhealthy_threshold = 3
  }
}

resource "aws_lb_target_group" "green" {
  # identical to blue
  name = "myapp-green"
  # ...
}

resource "aws_lb_listener_rule" "weighted" {
  listener_arn = aws_lb_listener.main.arn
  priority     = 100

  condition {
    path_pattern { values = ["/*"] }
  }

  action {
    type = "forward"
    forward {
      target_group {
        arn    = aws_lb_target_group.blue.arn
        weight = 100
      }
      target_group {
        arn    = aws_lb_target_group.green.arn
        weight = 0
      }
      stickiness {
        enabled  = true
        duration = 300  # keep users on the same TG during rollout
      }
    }
  }
}
Traffic shift procedure
# Canary: send 10% to green
aws elbv2 modify-rule --rule-arn <rule-arn> \
  --actions '[{"Type":"forward","ForwardConfig":{"TargetGroups":[{"TargetGroupArn":"<blue-arn>","Weight":90},{"TargetGroupArn":"<green-arn>","Weight":10}]}}]'

# Full cutover
aws elbv2 modify-rule --rule-arn <rule-arn> \
  --actions '[{"Type":"forward","ForwardConfig":{"TargetGroups":[{"TargetGroupArn":"<blue-arn>","Weight":0},{"TargetGroupArn":"<green-arn>","Weight":100}]}}]'

# Rollback: flip back to blue
aws elbv2 modify-rule --rule-arn <rule-arn> \
  --actions '[{"Type":"forward","ForwardConfig":{"TargetGroups":[{"TargetGroupArn":"<blue-arn>","Weight":100},{"TargetGroupArn":"<green-arn>","Weight":0}]}}]'

Or update weights via Terraform and apply. No appspec, no CodeDeploy IAM role, no separate control plane.

When to use CodeDeploy instead
  • Your team already has a CodeDeploy pipeline and wants to keep it
  • You need automatic rollback triggered by CloudWatch alarms without custom scripts
  • You need hooks (BeforeInstall, AfterInstall, AfterAllowTraffic) for migration/smoke steps

CodeDeploy ECS Blue/Green (Reference)

The Critical Non-Obvious Part: lifecycle.ignore_changes

When using CodeDeploy to manage ECS blue/green deployments, CodeDeploy dynamically swaps the ALB listener's default_action.target_group_arn between the blue and green target groups. If you don't suppress this in OpenTofu/Terraform, every subsequent tofu plan will show drift and try to restore the original target group — fighting with CodeDeploy on every deployment.

Fix: always add lifecycle.ignore_changes to the listener:

resource "aws_lb_listener" "bg_demo" {
  load_balancer_arn = aws_lb.main.arn
  port              = 8080
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.bg_demo_blue.arn  # initial state only
  }

  # CodeDeploy swaps default_action.target_group_arn between blue and green.
  # Without this, tofu plan constantly shows drift.
  lifecycle {
    ignore_changes = [default_action]
  }
}
Full Pattern
Two Target Groups (blue and green)
resource "aws_lb_target_group" "bg_demo_blue" {
  name        = "myapp-blue"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = var.vpc_id
  target_type = "ip"

  health_check {
    path                = "/health"
    interval            = 15
    healthy_threshold   = 2
    unhealthy_threshold = 3
  }

  deregistration_delay = 10  # short for faster deployments
}

resource "aws_lb_target_group" "bg_demo_green" {
  # identical to blue
  name = "myapp-green"
  # ...
}
CodeDeploy App + Deployment Group
resource "aws_iam_role" "codedeploy_ecs" {
  name = "myapp-codedeploy-ecs"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{ Effect = "Allow", Principal = { Service = "codedeploy.amazonaws.com" }, Action = "sts:AssumeRole" }]
  })
}

resource "aws_iam_role_policy_attachment" "codedeploy_ecs" {
  role       = aws_iam_role.codedeploy_ecs.name
  policy_arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS"
}

resource "aws_codedeploy_app" "app" {
  compute_platform = "ECS"
  name             = "myapp"
}

resource "aws_codedeploy_deployment_group" "app" {
  app_name              = aws_codedeploy_app.app.name
  deployment_group_name = "myapp-dg"
  service_role_arn      = aws_iam_role.codedeploy_ecs.arn

  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "BLUE_GREEN"
  }

  blue_green_deployment_config {
    deployment_ready_option {
      action_on_timeout = "CONTINUE_DEPLOYMENT"  # auto-shift, no manual confirmation
    }
    terminate_blue_instances_on_deployment_success {
      action                           = "TERMINATE"
      termination_wait_time_in_minutes = 5  # give blue tasks 5 min to finish in-flight requests
    }
  }

  # Traffic shift configs:
  # CodeDeployDefault.ECSCanary10Percent5Minutes  — 10% for 5 min, then 100%
  # CodeDeployDefault.ECSLinear10PercentEvery1Minutes — 10% per minute (10 min total)
  # CodeDeployDefault.ECSAllAtOnce               — immediate 100%
  deployment_config_name = "CodeDeployDefault.ECSCanary10Percent5Minutes"

  ecs_service {
    cluster_name = aws_ecs_cluster.main.name
    service_name = aws_ecs_service.app.name
  }

  load_balancer_info {
    target_group_pair_info {
      prod_traffic_route {
        listener_arns = [aws_lb_listener.bg_demo.arn]  # listener ARN, not rule ARN
      }
      target_group { name = aws_lb_target_group.bg_demo_blue.name }
      target_group { name = aws_lb_target_group.bg_demo_green.name }
    }
  }

  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM"]
  }

  alarm_configuration {
    enabled = true
    alarms  = [aws_cloudwatch_metric_alarm.blue_5xx.alarm_name, aws_cloudwatch_metric_alarm.green_5xx.alarm_name]
  }
}
CloudWatch Alarms for Auto-Rollback

When DEPLOYMENT_STOP_ON_ALARM is set, CodeDeploy monitors these alarms during the canary phase. If any alarm fires, the deployment stops and rolls back.

resource "aws_cloudwatch_metric_alarm" "blue_5xx" {
  alarm_name          = "myapp-blue-5xx"
  metric_name         = "HTTPCode_Target_5XX_Count"
  namespace           = "AWS/ApplicationELB"
  statistic           = "Sum"
  period              = 60
  evaluation_periods  = 1
  threshold           = 1   # production: use 5-10 to reduce false positives
  comparison_operator = "GreaterThanOrEqualToThreshold"
  treat_missing_data  = "notBreaching"
  dimensions = {
    LoadBalancer = aws_lb.main.arn_suffix
    TargetGroup  = aws_lb_target_group.bg_demo_blue.arn_suffix
  }
}

# identical for green
Deployment Trigger (appspec.json)

CodeDeploy needs an appspec.json that points to the new task definition. This is passed at deploy time, not managed by Terraform:

{
  "version": 1,
  "Resources": [{
    "TargetService": {
      "Type": "AWS::ECS::Service",
      "Properties": {
        "TaskDefinition": "<NEW_TASK_DEF_ARN>",
        "LoadBalancerInfo": {
          "ContainerName": "app",
          "ContainerPort": 8080
        }
      }
    }
  }]
}
aws deploy create-deployment \
  --application-name myapp \
  --deployment-group-name myapp-dg \
  --revision revisionType=AppSpecContent,appSpecContent={content="$(cat appspec.json)"}
Common Pitfalls
  • prod_traffic_route.listener_arns must be a listener ARN, not a listener rule ARN. Using a rule ARN here causes CodeDeploy to fail silently or with a confusing error.
  • Both blue and green target groups must have identical health check configuration. Mismatched healthy_threshold / unhealthy_threshold values will cause one TG to always be considered unhealthy.
  • desired_count on aws_ecs_service should not change during blue/green. CodeDeploy manages task counts independently during deployment.
  • CloudWatch alarm threshold for production: use 5–10 for threshold, not 1. A single stray 5xx during the canary window will trigger a rollback on production load.

Comments

Markdown supported