View all articles

Reducing the blast radius in AWS ECS with Canary Deployments

July 25, 2025
Wednesday Team

We’ve all been there, it’s 4 pm on a Wednesday afternoon(subtle plug) you’ve just deployed a major release to production, and your alerts channel on slack is blowing up, there are some non-critical but important workflows that don’t work as intended. You did everything right, you had tests, you had a robust pipeline, and everything was manually QA-ed too. How did this happen?

Well, that's the truth of it, you’ve built a product that caters to a thousand different flows and a hundred thousand different users. The impact of not handling a single edge case could be catastrophic. Notice the use of “could be” rather than “is”, it obviously means I’ve got an ace up my sleeve. Don’t worry, I’m not here to gloat, I’m here to share!

I understand that deploying a new release is always scary and daunting, and that’s where canary deployments help. In this deployment strategy, the new version of the software is rolled out in phases. You could start as low as 1% and have slow increments based on metrics, or you could manually choose to increase the percentage rollout. This allows you to control the potential blast radius of bugs, or issues that are seen only in production.

Let’s take a simple example. You’ve got v1 rolled out and being used. It’s a stable build but doesn’t have that new swanky feature the team spent the last 2 weeks building (Bi-weekly releases for the win).

Here’s what happens next. You release v2 at a 1% rollout. This means for every 100 requests that come in 1 request is served by v2, and the remaining 99 are served by v1.

💡 Hah! thought you’d be able to bypass backward compatibility, is it? Backward compatibility, & a rollback strategy are not luxuries, they come under essentials. No matter what happens, the customer experience should never suffer. This will ensure it.

The impacts of breakages, unintended latencies, and other side effects that come with a new release are now limited to a percentage of your users rather than the entire user group. This is a step ahead of observability and proactive response to errors, which is crucial for this approach to work btw. Canary deployments are useless if you don’t have the ability to monitor the state of your application or the health of your system. If you haven’t already read our in-depth article to set up Signoz for application performance monitoring I would highly recommend that you do it here. Or if you’d like to avoid that simply set up cloudwatch logs, metrics, and Xray. But make sure you have an insight into the performance, behavior, and health of your system!

Alright, enough talking about the magic of canary deployments, let’s get practical. Through this article, I intend to lay down a 10-step process that will allow you to enable canary deployments for ECS workloads. I’ve even included the link to a GitHub repository where I’ve open-sourced the entire setup. Even better I’ve added a CD pipeline and a configuration that gives you full control of traffic switching, and annotating stable builds!

Follow along and I hope to provide more value than you originally anticipated. And by following the steps outlined in this article will result in the implementation of the following architecture.

Diagram: internet to load balancer to Envoy gateway, then App Mesh router splitting 50/50 to ECS services svc1-v1 and svc1-v2

Pre-cursor

In order to ease the process we’ve created some scripts for you. Run the following command:



git clone https://github.com/wednesday-solutions/ecs-appmesh-starter

In case you decide to go cowboy and mix things up, please make sure you rename it throughout the repo. The scripts in this repo assume the following values

AWS

Account ID

   <AWS_ACCOUNT_ID> → change this to your Account ID

AWS Region

   ap-south-1

App Mesh

AppMesh Name

   the-wednesday-super-app

Virtual Node

   the-wednesday-super-app-virtual-node-1

   the-wednesday-super-app-virtual-node-2

Virtual Router

   the-wednesday-super-app-virtual-router

Route

   the-wednesday-super-app-route

Virtual Gateway

   the-wednesday-super-app-virtual-gateway

Virtual Gateway Route

   the-wednesday-super-app-gateway-route

ECS

Service1 v1

   the-wednesday-super-app-svc1-v1 (task-definition family is the same)

Service1 v2

   the-wednesday-super-app-svc1-v2 (task-definition family is the same)

Gateway Service

   the-wednesday-super-app-gateway-service (task-definition family is the same)

ECR

Service1v1 repository name

  the-wednesday-super-app-svc1-v1

Service1v2 repository name

   the-wednesday-super-app-svc1-v2

‍

💡 Hey there! Just a heads up before you get started, if you want to personalize your app's properties, just make sure that you have replaced the-wednesday-super-app with your-app-name in the assets folder in the repository. Please be consistent with the names throughout the tutorial to avoid issues.

‍

Step 1: Create an App Mesh

Create an AppMesh. Just search AppMesh on the AWS Console and create one. Give it a meaningful name.  ExampleAppMesh is acceptable but frowned upon 😖

AWS App Mesh Create mesh form with mesh name the-wednesday-super-app and Deny external traffic selected

Step 2: Create Virtual Nodes

Stay with me on this, now is where it gets exciting and interesting. Create 2 Virtual Nodes. 2 since at any point we’d like a maximum of 2 versions running in our canary deployment. Assuming you’ve got a single service, you’re going to want to be able to route traffic between these 2 variants.

Empty Virtual nodes list in the the-wednesday-super-app mesh with a Create virtual node button
Virtual node 1 configuration using DNS discovery with hostname svc1-v1.the-wednesday-super-app
Virtual node listener set to http on port 9000, with the Create virtual node button
💡 Be careful while setting the hostname. It should reflect the CloudMap namespace and the service. Else your gateway won’t be able to route traffic to your nodes properly
GIF of Thor smashing a mug in a diner with the caption ANOTHER!

‍

Create your second virtual node now

Create virtual node form for virtual-node-2 with DNS hostname svc1-v2.the-wednesday-super-app
Listener configuration for the second virtual node on http port 9000

‍

Step 3: Create a Virtual Router

From the same console, create a virtual router on the port that your container expects traffic on

Create virtual router form naming the-wednesday-super-app-virtual-router with an http:9000 listener

Step 4: Create and configure a Virtual Service

Create a Virtual Service now, and add the VirtualRouter created above as the provider.

Virtual service the-wednesday-super-app-svc1.the-wednesday-super-app.local with the virtual router as provider
💡 Be careful while naming the service. It should be the service discovery name of the real service that you’re targeting.

Step 5: Create an AppMesh Route

Finally, create an AppMesh Route. This will have “WeightedTargets” that will allow you to accomplish the goal of phased releases/canary deployments. This is where you specify the weight or percentage of traffic that you wish a certain version will handle.

Step 6: Create a Virtual Gateway

Phew, captain! You’ve come a long way, just a few more yards, don’t you give up on me now. It’s darkest before dawn and all that.

Create a VirtualGateway, ensure that the Port that you’re using is 9080, the protocol is http

Step 7: Create a Virtual Gateway

It’s the last leg now for Part 1, create a GatewayRoute. Ensure that the target is the VirtualService that we created previously!

Gateway route the-wednesday-super-app-gateway-route, type http, targeting the svc1 virtual service on port 9000

Step 8: Create an ECS Cluster and the Service for Version 1

https://media.giphy.com/media/3o6fJ1BM7R2EBRDnxK/giphy.gif

Great job! You’ve created and configured your AppMesh. You’re ready to take on the world, err, I think I forgot something. Ah the ECS! Go on now, create your cluster. (If you already have a cluster, that's great. Move on to creating services)

ECS cluster form named the-wednesday-super-app on the default VPC with three ap-south-1 subnets selected
Create new VPC by clicking on the "VPC Console" link
AWS VPC settings with "VPC and more" selected, the-wednesday-super-app name tag, 10.0.0.0/16 CIDR, and 4 subnets preview
AWS VPC creation success log listing the new VPC, four subnets, internet gateway, route tables, and S3 endpoint
Configure the network
ECS cluster network settings using the-wednesday-super-app VPC, its four public and private subnets, and a default namespace
Go now, create the cluster.
💡 Pro tip: If you’re not already using it please consider using AWS Copilot for everything ECS. It’s a gift you didn’t know you needed. Short story - that’s how I can create 3 clusters (dev, QA, prod) with multiple services, databases, cache clusters, and an MSK in under an hour. Take a look at this article and get your first copilot-powered ECS cluster up and running!

We’ll be creating 2 services that will be used to deploy multiple versions of the same application at the same time, and hence achieve the goal of canary deployments.

https://media.giphy.com/media/l3mZcxZKIQemTeT9S/giphy.gif

Make sure your AWS profile is configured and run the following command



./scripts/create-roles-and-policies.sh

Terminal run of create-roles-and-policies.sh making IAM task and execution roles with ECR, CloudWatch, and App Mesh policies

PS: You can skip the below section if you’ve already got an ECR repo ready. Be sure to change the image location in the task definition below!

Creating an ECR repository

Authenticate with ECR



aws ecr get-login-password --region ap-south-1 | \\docker login --username AWS \\--password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.ap-south-1.amazonaws.com

For v1

Create the repository



aws ecr create-repository \\--repository-name=the-wednesday-super-app-svc1-v1

Build your application image



docker build -t \\<AWS_ACCOUNT_ID>.dkr.ecr.ap-south-1.amazonaws.com/the-wednesday-super-app-svc1-v1:latest .

Push to the repository



docker push \
<AWS_ACCOUNT_ID>.dkr.ecr.ap-south-1.amazonaws.com/the-wednesday-super-app-svc1-v1:latest

For v2

Change line 5 in index.js as follows



const version = "v2"

Create the repository



aws ecr create-repository \
--repository-name=the-wednesday-super-app-svc1-v2

Build your application image



docker build -t \
<AWS_ACCOUNT_ID>.dkr.ecr.ap-south-1.amazonaws.com/the-wednesday-super-app-svc1-v2:latest .

Push to the repository



docker push \
<AWS_ACCOUNT_ID>.dkr.ecr.ap-south-1.amazonaws.com/the-wednesday-super-app-svc1-v2:latest

Before you start with your first service you’re going to need a task definition for it, this is where the magic is

Copy the contents from assets/task-definitions/svc1-v1.json and create a new task definition with it

ECS "Create new task definition" JSON editor with svc1-v1 config: svcTaskExecutionRole, awslogs, ports 9000 and 3000
ECS create service form using the svc1-v1 task definition on the app-mesh-console-demo cluster as a REPLICA service
New security group the-wednesday-super-app-sg with inbound TCP rules for ports 9901, 9080, 9000, and HTTP 80
Same service discovery form for svc1-v1 with ECS task health propagation enabled before finishing service creation
Same service discovery form for svc1-v1 with ECS task health propagation enabled before finishing service creation
Follow through with the form and create the service. Go to your ECS cluster, you should now see a new service.
ECS cluster Services tab listing the-wednesday-super-app-svc1-v1 as an ACTIVE Fargate replica service
Click on tasks tab to the left, and you should see a new task is created.
ECS task details for the svc1-v1 Fargate task, status ACTIVATING with desired status RUNNING and svcTaskRole
💡 Make sure you’re using the old UI. As of writing this article, the new console UI has some glitches

Step 9: Create ECS Service for Version 2

Now let’s configure the second service. It’s the same process again. Easy right?

https://media.giphy.com/media/hV1PXxlRh6tA8drgyD/giphy.gif

Copy the contents from assets/task-definitions/svc1-v2.json and create a new task definition with it

Create the 2nd service and attach the newly created task definition as shown below

ECS create service form for the second service, using the svc1-v2 task definition on the-wednesday-super-app cluster
Network settings for the svc1-v2 service: cluster VPC, two public subnets, existing security group, public IP enabled
Service discovery for svc1-v2 reusing the existing private the-wednesday-super-app namespace
ECS launch status confirming the service discovery service and the svc1-v2 service were created
Look at that, another service, another task 🔥
ECS task details for the new svc1-v2 Fargate task, health status HEALTHY, with awsvpc network and private IP
Containers section of the svc1-v2 task showing the app container running alongside an envoy sidecar

Step 10: Create the ECS Service Gateway

That was the tough part, comrade. You’ve got your ECS cluster, services & task definition envoy sidecar ready. You also have your AppMesh, VirtualService, VirtualRoute, and VirtualGateway ready. All you need now is a service gateway. Don’t worry I’ve got you.

We’re going to provision another ECS service, this one will only be running an envoy proxy.

Copy the contents from assets/task-definitions/svc-gateway.json and let’s get cracking!

Use the New UI for creating this service, it’ll allow you to create the Load Balancer and TG easily.

New ECS console deployment configuration creating the replica service the-wednesday-super-app-service-gateway
Service Connect enabled for the gateway service in client-side-only mode within the-wednesday-super-app namespace
Service Connect log collection sending gateway Envoy logs to Amazon CloudWatch with awslogs group, region, and prefix
AWS ECS service Networking settings with the app VPC, two public subnets, an existing security group and public IP on
ECS load balancing settings creating a new Application Load Balancer for service-gateway with an HTTP listener on port 80
Overheard @ Wednesday

A monthly letter from an AI native agency.

What we learn helping businesses put AI to work and build products that win.