The most surprising thing about ECS blue-green deployments is that they don’t actually deploy anything new; they redirect traffic.

Let’s watch it happen. Imagine we have a live ECS service, my-app-service, running version v1 of our my-app-task definition. We want to deploy v2.

First, we set up a CodeDeploy application and deployment group.

aws deploy create-application --application-name my-app --compute-platform ECS
aws deploy create-deployment-group --application-name my-app --deployment-group-name my-app-dg --service-role-arn arn:aws:iam::123456789012:role/CodeDeployServiceRole --ecs-services my-app-service --region us-east-1

Now, we tell CodeDeploy about our ECS service and which task definition revision to use for the new version. CodeDeploy will create a new ECS task definition revision, let’s call it my-app-task:2, based on our desired changes.

aws deploy create-deployment --application-name my-app --deployment-group-name my-app-dg --description "Deploying v2" --revision '{"revisionType": "AppSpecContent", "appSpecContent": {"content": "version: 0.0\nResources:\n  - TargetService:\n      Type: AWS::ECS::Service\n      Properties:\n        - ServiceName: my-app-service\n        - TaskDefinition: arn:aws:ecs:us-east-1:123456789012:task-definition/my-app-task:2"}}' --region us-east-1

CodeDeploy then spins up a new set of tasks using my-app-task:2. These new tasks are registered with a new load balancer target group, let’s say TG-B. The old tasks (v1) remain running and registered with the original target group, TG-A. At this point, TG-A is receiving 100% of the traffic.

The magic happens in the hooks section of the appspec.yml that CodeDeploy uses (or that we provided inline above). CodeDeploy doesn’t just deploy and leave; it orchestrates a gradual traffic shift.

version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        - ServiceName: my-app-service
        - TaskDefinition: arn:aws:ecs:us-east-1:123456789012:task-definition/my-app-task:2
        - LoadBalancerInfo:
            - TargetGroupInfoList:
              - TargetGroupArn: arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/TG-B/abcdef1234567890
Hooks:
  - BeforeAllowTraffic: 'LambdaFunction: MyBeforeAllowTrafficLambda'
  - AfterAllowTraffic: 'LambdaFunction: MyAfterAllowTrafficLambda'

CodeDeploy first waits for the new tasks in TG-B to become healthy. Once they are, it executes the BeforeAllowTraffic hook (if defined). Then, it initiates the traffic shift. By default, CodeDeploy shifts 10% of traffic to TG-B for a few minutes, monitors for errors, and then shifts another 10%, and so on, until 100% of traffic is hitting TG-B. During this process, TG-A (running v1) continues to serve its portion of traffic. This slow shift allows you to catch issues early without impacting all users.

After the traffic is fully shifted, CodeDeploy runs the AfterAllowTraffic hook. If all checks pass, the old tasks associated with TG-A are then gracefully terminated. The ECS service itself is updated to point to TG-B as its primary target group. The next time you initiate a deployment, CodeDeploy will create a new task definition revision (v3), spin up tasks for v3 in a new target group (TG-C), and repeat the traffic shifting process from TG-B to TG-C.

The key insight here is that CodeDeploy doesn’t modify your running ECS service in place. It orchestrates the creation of a parallel environment, shifts traffic, and then tears down the old. You control the traffic shift speed and the validation steps via hooks and the CodeDeploy console.

The next thing you’ll likely want to configure is custom validation steps within the traffic shifting phases using Lambda functions.

Want structured learning?

Take the full Ecs course →