Blue-green deployment is a release strategy that eliminates downtime by running two identical production environments — blue (current version) and green (new version). Traffic switches from blue to green once the new version passes health checks. If anything goes wrong, you switch back to blue in seconds. This project implements blue-green deployments on AWS ECS Fargate using AWS CodeDeploy to manage the traffic shift, an Application Load Balancer with two target groups, and a GitHub Actions pipeline that triggers automatic deployments on every merge to main. This is how most Indian mid-stage startups (Razorpay, Cleartax, Meesho) deploy containerised applications before moving to Kubernetes — ECS Fargate with blue-green is production-grade, cost-effective, and significantly simpler to operate than Kubernetes. GitHub Push to main | v GitHub Actions CI (build, test, push to ECR) | v AWS CodeDeploy (manages the shift) | +------+------+ | | v v Blue TG Green TG (old v1) (new v2) | | +------+------+ | v Application Load Balancer (ALB shifts weight from blue to green) | v Internet
A traditional deployment on ECS works like this — stop the old containers, start the new ones, users see errors during the gap. Even if the gap is 30 seconds, that is 30 seconds of 503 errors for every user. During peak traffic at Swiggy during lunch hour, 30 seconds of downtime means thousands of failed orders. Blue-green solves this by never stopping the old version until the new version is fully healthy. The ALB shifts traffic from the blue target group to the green target group over a configurable time period. If health checks fail during the shift, CodeDeploy automatically rolls back to blue with zero manual intervention.
### Step 1: Provision ECS Infrastructure with Terraform ```bash mkdir ecs-blue-green && cd ecs-blue-green mkdir -p {modules/{vpc,ecs,alb,iam},environments/production} ``` Create `environments/production/main.tf`: ```hcl terraform { required_version = ">= 1.7.0" backend "s3" { bucket = "ecs-bg-state-YOUR_ACCOUNT_ID" key = "production/terraform.tfstate" region = "ap-south-1" encrypt = true dynamodb_table = "ecs-bg-lock" } } provider "aws" { region = "ap-south-1" } ## VPC with 2 public subnets for ALB and 2 private subnets for ECS tasks resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true tags = { Name = "ecs-bg-vpc" } } resource "aws_subnet" "public" { count = 2 vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index) availability_zone = ["ap-south-1a", "ap-south-1b"][count.index] map_public_ip_on_launch = true tags = { Name = "ecs-bg-public-${count.index}" } } resource "aws_subnet" "private" { count = 2 vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index + 4) availability_zone = ["ap-south-1a", "ap-south-1b"][count.index] tags = { Name = "ecs-bg-private-${count.index}" } } ## ECS Cluster resource "aws_ecs_cluster" "main" { name = "production-cluster" setting { name = "containerInsights" value = "enabled" # CloudWatch container metrics } } ## ALB with two target groups — one for blue, one for green resource "aws_lb" "main" { name = "ecs-bg-alb" internal = false load_balancer_type = "application" security_groups = [aws_security_group.alb.id] subnets = aws_subnet.public[*].id } resource "aws_lb_target_group" "blue" { name = "ecs-bg-blue" port = 8080 protocol = "HTTP" vpc_id = aws_vpc.main.id target_type = "ip" # ECS Fargate uses IP-based target registration health_check { path = "/health" healthy_threshold = 2 unhealthy_threshold = 5 timeout = 10 interval = 30 } } resource "aws_lb_target_group" "green" { name = "ecs-bg-green" port = 8080 protocol = "HTTP" vpc_id = aws_vpc.main.id target_type = "ip" health_check { path = "/health" healthy_threshold = 2 unhealthy_threshold = 5 timeout = 10 interval = 30 } } ## Listener starts with all traffic on blue resource "aws_lb_listener" "http" { load_balancer_arn = aws_lb.main.arn port = 80 protocol = "HTTP" default_action { type = "forward" target_group_arn = aws_lb_target_group.blue.arn } } ## ECS Task Definition resource "aws_ecs_task_definition" "app" { family = "webapp" network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] cpu = 256 memory = 512 execution_role_arn = aws_iam_role.ecs_execution.arn container_definitions = jsonencode([{ name = "webapp" image = "${aws_ecr_repository.app.repository_url}:latest" portMappings = [{ containerPort = 8080 protocol = "tcp" }] logConfiguration = { logDriver = "awslogs" options = { "awslogs-group" = "/ecs/webapp" "awslogs-region" = "ap-south-1" "awslogs-stream-prefix" = "ecs" } } }]) } ## ECS Service — CodeDeploy manages the blue-green switch resource "aws_ecs_service" "app" { name = "webapp" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.app.arn desired_count = 2 launch_type = "FARGATE" network_configuration { subnets = aws_subnet.private[*].id security_groups = [aws_security_group.ecs_tasks.id] assign_public_ip = false } load_balancer { target_group_arn = aws_lb_target_group.blue.arn container_name = "webapp" container_port = 8080 } deployment_controller { type = "CODE_DEPLOY" # This tells ECS that CodeDeploy manages deployments } lifecycle { ignore_changes = [task_definition, load_balancer] # CodeDeploy manages these — Terraform should not fight with it } } ``` ### Step 2: Configure AWS CodeDeploy for Blue-Green ```hcl ## CodeDeploy Application resource "aws_codedeploy_app" "app" { compute_platform = "ECS" name = "webapp-deployment" } ## Deployment Group — this is where the blue-green magic happens resource "aws_codedeploy_deployment_group" "app" { app_name = aws_codedeploy_app.app.name deployment_group_name = "webapp-production" service_role_arn = aws_iam_role.codedeploy.arn deployment_config_name = "CodeDeployDefault.ECSLinear10PercentEvery1Minutes" # This shifts 10% of traffic every minute # Full shift completes in 10 minutes # Other options: # CodeDeployDefault.ECSAllAtOnce — instant (risky for production) # CodeDeployDefault.ECSLinear10PercentEvery3Minutes — slower, safer auto_rollback_configuration { enabled = true events = ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM"] } blue_green_deployment_config { deployment_ready_option { action_on_timeout = "CONTINUE_DEPLOYMENT" # Auto-proceed after health checks pass } terminate_blue_instances_on_deployment_success { action = "TERMINATE" termination_wait_time_in_minutes = 5 # Keep blue running for 5 mins after green is live # This gives you time to manually roll back if you notice issues } } 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.http.arn] } target_group { name = aws_lb_target_group.blue.name } target_group { name = aws_lb_target_group.green.name } } } } ``` ### Step 3: GitHub Actions Pipeline Create `.github/workflows/deploy.yml`: ```yaml name: Deploy to ECS — Blue-Green on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: * uses: actions/checkout@v4 * name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-deploy-role aws-region: ap-south-1 * name: Login to ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 * name: Build and push Docker image id: build env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ github.sha }} run: | docker build -t $ECR_REGISTRY/webapp:$IMAGE_TAG . docker push $ECR_REGISTRY/webapp:$IMAGE_TAG echo "image=$ECR_REGISTRY/webapp:$IMAGE_TAG" >> $GITHUB_OUTPUT * name: Update ECS Task Definition with new image id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: task-definition: task-definition.json container-name: webapp image: ${{ steps.build.outputs.image }} * name: Create CodeDeploy deployment (triggers blue-green shift) uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.task-def.outputs.task-definition }} service: webapp cluster: production-cluster codedeploy-appspec: appspec.yml codedeploy-application: webapp-deployment codedeploy-deployment-group: webapp-production wait-for-service-stability: true ``` Create `appspec.yml` in your repository root: ```yaml version: 0.0 Resources: * TargetService: Type: AWS::ECS::Service Properties: TaskDefinition: <TASK_DEFINITION> LoadBalancerInfo: ContainerName: webapp ContainerPort: 8080 PlatformVersion: LATEST ```
```bash ## 1. Verify ECS service is running with desired count aws ecs describe-services \ --cluster production-cluster \ --services webapp \ --query 'services[0].[runningCount,desiredCount,deployments]' ## Expected: runningCount == desiredCount, one active deployment ## 2. Test the application is responding through the ALB ALB_DNS=$(terraform output -raw alb_dns_name) curl -I http://$ALB_DNS ## Expected: HTTP 200 ## 3. Trigger a deployment by pushing to main git commit --allow-empty -m "trigger: blue-green deployment test" git push origin main ## 4. Watch the CodeDeploy deployment in the AWS console ## CodeDeploy -> Deployments -> watch traffic shift from 0% to 100% on green ## 5. Verify traffic is shifting ## Run this in a loop during the deployment: for i in {1..60}; do curl -s http://$ALB_DNS/version && sleep 5 done ## During the shift you should see responses from both old and new version ## 6. Test automatic rollback ## Deploy a version with a broken health endpoint ## CodeDeploy will detect health check failures and roll back automatically ## Check the CodeDeploy console — deployment status should show ROLLED_BACK ## 7. Verify zero downtime ## Run constant traffic during a deployment: while true; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://$ALB_DNS) echo "$(date): $STATUS" sleep 1 done ## Expected: All 200 responses throughout the entire deployment — zero 5xx errors ```
Blue-green deployment is a release strategy that eliminates downtime by running two identical production environments — ...
A traditional deployment on ECS works like this — stop the old containers, start the new ones, users see errors during t...
Step 1: Provision ECS Infrastructure with Terraform Create environments/production/main.tf: Step 2: Configure AWS CodeDe...
...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.