AWS Builder Center
 ECS Blue/Green Deployments No Longer Need CodeDeploy

ECS Blue/Green Deployments No Longer Need CodeDeploy

For years, blue/green on ECS meant bolting CodeDeploy onto the side: a second service, an appspec.yaml, extra IAM roles. ECS does all of it natively now. Here's the new config, the lifecycle hooks, and what to know before you delete the CodeDeploy glue.

Cloud Engineer
For years, every blue/green deployment I ran on ECS had a second service bolted onto the side of it: AWS CodeDeploy. ECS ran the tasks. CodeDeploy owned the traffic shift, the bake window, and the rollback. Two services, two mental models, two sets of IAM roles, and an  appspec.yaml  that existed only to translate between them.
ECS now does all of it natively. You set the deployment strategy on the service, point it at two target groups, and ECS handles the traffic shift, the bake time, the CloudWatch-alarm rollback, and the lifecycle hooks itself. No CodeDeploy application. No deployment group. No  appspec.yaml .
If you have been maintaining that CodeDeploy glue, you can delete it.

What Actually Changed

Native blue/green landed on ECS in July 2025. Linear and canary strategies followed in May 2026, so the feature set now covers the traffic-shifting patterns people used CodeDeploy for.
The old architecture split responsibility across two services:
  • ECS managed the task sets and registered them to target groups.
  • CodeDeploy watched the deployment, shifted the ALB listener between target groups, ran the bake period, checked alarms, and triggered rollback.
The wiring between them was the annoying part. You needed a CodeDeploy application, a deployment group, an  appspec.yaml  describing the container and port, a separate CodeDeploy IAM role, and a deployment controller set to  CODE_DEPLOY  on the service. Getting a new service into blue/green meant getting all of that right before your first deploy worked.
Native blue/green folds every one of those responsibilities into ECS. The traffic shift, bake period, managed rollback, and lifecycle hooks are ECS features now, configured in the service definition.
blue/green


How It Works

ECS-native blue/green provisions a green service revision alongside the running blue revision and registers it to a separate target group behind your load balancer listener. Traffic shifts from blue to green using the strategy you pick, both revisions run together for a configurable bake period, and then ECS retires blue or rolls back if an alarm or hook fails.
The traffic shift itself uses Elastic Load Balancing weighted target groups. ECS adjusts the weights. For managed traffic shifting you need an Application Load Balancer, a Network Load Balancer, or Service Connect. A headless service (no load balancer) can still use blue/green for controlled rollouts, but ECS won't shift traffic for it because there's no traffic to shift.
Four strategies are available, and they differ only in how the weight moves:
Strategy
Traffic move
Rollback
Cost during deploy
Good for
Rolling
Task-by-task, no shift
Slow (redeploy)
Low
Cost-sensitive, simple services
Blue/green
0% to 100% in one switch
Instant
2x tasks
DB migrations, major upgrades
Linear
Equal increments with bake between each
Instant
2x tasks
APIs, microservices
Canary
Small slice first, then the rest
Instant
2x tasks
Changes needing careful validation, ML models
Rolling is the default and does no traffic shifting. The other three keep the blue revision alive during the bake window, which is what makes rollback instant: ECS shifts the weights back instead of redeploying tasks.
traffic Shift


The Config

This is the part that shows the difference best. The whole deployment behavior lives in the service definition now.
You set the deployment controller to  ECS  (not  CODE_DEPLOY ) and choose a strategy in the deployment configuration:
{
"deploymentController": { "type": "ECS" },
"deploymentConfiguration": {
"strategy": "BLUE_GREEN",
"bakeTimeInMinutes": 10,
"alarms": {
"alarmNames": ["ecs-5xx-errors", "ecs-p99-latency"],
"rollback": true,
"enable": true
}
}
}
For a canary rollout, you swap the strategy and add the canary shape:
{
"deploymentConfiguration": {
"strategy": "CANARY",
"canaryConfiguration": {
"canaryPercent": 5.0,
"canaryBakeTimeInMinutes": 15
},
"alarms": {
"alarmNames": ["ecs-5xx-errors", "checkout-error-rate"],
"rollback": true,
"enable": true
}
}
}
Linear works the same way with a  linearConfiguration  instead:  stepPercent  (percentage shifted per step, default 10.0) and  stepBakeTimeInMinutes  (wait between steps, default 6).
The alarm block is what replaces CodeDeploy's rollback logic. When any listed CloudWatch alarm enters the  ALARM  state during the deployment, ECS rolls back. You point it at the metrics that actually indicate breakage, usually  HTTPCode_Target_5XX_Count ,  TargetResponseTime , and any custom business metric you publish.

Lifecycle Hooks

CodeDeploy's  appspec.yaml  hooks (BeforeAllowTraffic, AfterAllowTraffic, and friends) were how you ran validation between traffic-shift steps. ECS has its own lifecycle hooks now, and they cover more of the deployment than CodeDeploy did.
A hook is a Lambda function wired to one or more stages. The Lambda returns  SUCCEEDED  to let the deployment continue,  IN_PROGRESS  while it's still checking, or  FAILED  to trigger a rollback.
The stages, in order:
Stage
State when it runs
 RECONCILE_SERVICE 
Only when a new deployment starts with more than one revision already active
 PRE_SCALE_UP 
Green not started yet, blue serving 100%
 POST_SCALE_UP 
Green started, blue still serving 100%, no test traffic
 TEST_TRAFFIC_SHIFT 
Green ramping from 0% to 100% of test traffic
 POST_TEST_TRAFFIC_SHIFT 
Green serving 100% of test traffic
 PRE_PRODUCTION_TRAFFIC_SHIFT 
Before each production shift step
 PRODUCTION_TRAFFIC_SHIFT 
Production traffic moving to green
 POST_PRODUCTION_TRAFFIC_SHIFT 
Production shift complete
For linear and canary, the  PRE_PRODUCTION_TRAFFIC_SHIFT  and  PRODUCTION_TRAFFIC_SHIFT  stages fire at every increment, so a validation hook there runs on each step, not just once.
One constraint worth knowing up front:  PAUSE  hooks (the kind that just hold the deployment for manual approval) can't be attached to  TEST_TRAFFIC_SHIFT  or  PRODUCTION_TRAFFIC_SHIFT . Those two stages only accept Lambda hooks. Pause where the deployment is at rest, validate with Lambda where traffic is moving.
A minimal validation hook looks like this:
def handler(event, context):
stage = event["lifecycleStage"]
green_arn = event["executionDetails"]["targetServiceRevisionArn"]

if not smoke_test_passed(green_arn):
return {"hookStatus": "FAILED"}

return {"hookStatus": "SUCCEEDED"}
ECS passes the traffic weights and the target revision ARN in the event, so the hook knows exactly which revision to test and how much traffic it's holding.
Horizontal deployment timeline with the 8 lifecycle stages


What You Have to Set Up

The load balancer side is where the real resources live. For an ALB-backed service you need:
  • Two target groups: one for blue, one for green
  • A production listener with a listener rule that ECS shifts weight across (you pass the rule ARN, not the listener ARN)
  • Optionally, a test listener so hooks can validate green before it takes production traffic
  • An IAM role that lets ECS modify the load balancer during a shift. The managed policy is  AmazonECSInfrastructureRolePolicyForLoadBalancers  (it grants  elasticloadbalancing:ModifyListener ,  ModifyRule ,  RegisterTargets ,  DeregisterTargets , and the matching describe calls). ECS assumes this role to move the weights.
  • The service deployment controller set to  ECS 
  • The deployment strategy set to  BLUE_GREEN ,  CANARY , or  LINEAR 
  • Bake time, alarms, and hooks as needed
The piece that trips people up is wiring the two target groups to the service. It lives in the load balancer block of the service definition, under  advancedConfiguration :
{
"loadBalancers": [
{
"containerName": "web",
"containerPort": 80,
"targetGroupArn": "<blue target group>",
"advancedConfiguration": {
"alternateTargetGroupArn": "<green target group>",
"productionListenerRule": "<listener rule ARN>",
"roleArn": "<the ELB-modify role above>"
}
}
]
}
 targetGroupArn  is blue,  alternateTargetGroupArn  is green, and  productionListenerRule  is the rule ECS rewrites to shift traffic. Miss the  roleArn  or the rule ARN and service creation fails.
One thing that surprises people on the very first deploy: there's no existing "blue" yet, so ECS places the initial revision and sets the production rule weights itself. In practice the first task can land in the green target group at 100% weight while blue sits at 0. That's expected. ECS alternates blue and green across deployments, so which color is "live" flips each time.
If you're already running an ALB blue/green setup through CodeDeploy, you mostly have these pieces. The migration is deleting the CodeDeploy application, deployment group,  appspec.yaml , and CodeDeploy IAM role, then moving the traffic-shift settings onto the ECS service definition.
In CDK the whole thing collapses into the  FargateService  construct:
service = ecs.FargateService(self, "Service",
cluster=cluster,
task_definition=task_definition,
deployment_strategy=ecs.DeploymentStrategy.BLUE_GREEN,
)

service.add_lifecycle_hook(
ecs.DeploymentLifecycleLambdaTarget(
lambda_hook, "PreScaleHook",
lifecycle_stages=[ecs.DeploymentLifecycleStage.PRE_SCALE_UP],
)
)

When Native Blue/Green Is the Right Call

Use native ECS blue/green when:
  • You're on ECS and want traffic-shifted deploys without running a second service
  • You're starting a new service and don't want to learn CodeDeploy's model
  • You want canary or linear rollouts with alarm-based rollback
  • Your validation logic fits Lambda hooks at defined stages
  • You're already maintaining CodeDeploy glue you'd rather delete
Stay on rolling deployments when:
  • You can't afford 2x task capacity during a deploy
  • The service tolerates task-by-task replacement fine
  • You don't need traffic-level control or instant rollback
Keep CodeDeploy only when:
  • You have deployment tooling built around CodeDeploy's APIs and events that isn't worth rewriting yet
  • You coordinate ECS deploys with other CodeDeploy-managed platforms (Lambda, EC2) in one pipeline and want a single control plane
For a greenfield ECS service, there's little reason to reach for CodeDeploy anymore. The native path is fewer resources and one service to reason about.

Things to Know Before You Migrate

You pay for 2x tasks during the deploy. Blue and green both run through the bake window. A long bake on a large service is real money for the duration. Size the bake time to how long your metrics take to surface a problem, not longer.
Rollback speed depends on the bake window. Instant rollback works because blue is still running. Once the bake period ends and blue is retired, "rollback" means a fresh deploy of the old revision. Don't set the bake time shorter than your slowest meaningful alarm.
Alarms must already exist and cover both target groups. ECS rolls back on alarm state, so an alarm that doesn't fire on the failure mode you care about gives you false confidence. Alarm on 5XX rate, latency percentiles, and at least one business metric that reflects the user experience.
Hooks that shift-path run on rollback too. Production and test traffic-shift hooks are invoked on the rollback path as well. If a hook is slow, it adds latency to your rollback. Keep shift-stage hooks fast.
PAUSE hooks are limited. You can't pause at the test or production traffic-shift stages. Put manual approval gates at the scale-up or post-shift stages instead.
Headless services don't get managed traffic shifting. No load balancer or Service Connect means no weighted shift. You can still use the deployment structure, but ECS won't move traffic for you.
Express Mode services can't do this. If you created your service through ECS Express Mode (the simplified experience that provisions the ALB, target groups, and scaling for you), you can't switch it to blue/green. Express manages deployments itself, and the console blocks the change with "Deployment controller cannot be updated for Express services." The deployment-strategy panel is read-only there. Everything in this post applies to standard services. If you want to pick and tune the strategy yourself, create a standard service, not an Express one.

The Takeaway

Blue/green on ECS used to be a two-service arrangement held together with an  appspec.yaml . The deployment lived half in ECS and half in CodeDeploy, and the seam between them was where the setup pain and the debugging pain both lived.
Now the deployment lives in one place. You describe the strategy, the bake time, the alarms, and the hooks on the service, and ECS runs the whole thing. For a new service, skip CodeDeploy. For an existing one, the migration is mostly deletion.
Set the controller to  ECS . Pick a strategy. Point it at two target groups. Ship.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article