<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gbenga Ogbonyenitan</title>
    <description>The latest articles on DEV Community by Gbenga Ogbonyenitan (@gbengard).</description>
    <link>https://dev.to/gbengard</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4142623%2F9b243bc4-ecf2-41f0-8315-99141af93eb1.png</url>
      <title>DEV Community: Gbenga Ogbonyenitan</title>
      <link>https://dev.to/gbengard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gbengard"/>
    <language>en</language>
    <item>
      <title>Test failover in your CI/CD pipeline with AWS Fault Injection Service</title>
      <dc:creator>Gbenga Ogbonyenitan</dc:creator>
      <pubDate>Mon, 02 Nov 2026 22:35:00 +0000</pubDate>
      <link>https://dev.to/gbengard/test-failover-in-your-cicd-pipeline-with-aws-fault-injection-service-588n</link>
      <guid>https://dev.to/gbengard/test-failover-in-your-cicd-pipeline-with-aws-fault-injection-service-588n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When a task in an ECS service stops, ECS starts a new one by itself. You do not have to do anything. The load balancer then sends traffic to the new task when it is healthy.&lt;/p&gt;

&lt;p&gt;That is the design. But a design is a claim until you test it. These things can still go wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The new task cannot start. For example, the image cannot be pulled or a setting is missing.&lt;/li&gt;
&lt;li&gt;The new task starts but never passes the load balancer health check.&lt;/li&gt;
&lt;li&gt;The service recovers, but it takes longer than you can accept.&lt;/li&gt;
&lt;li&gt;The service runs only one task, so users see an outage while the replacement starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams find these problems during a real outage. In this guide, you find them on every release instead.&lt;/p&gt;

&lt;p&gt;You build a pipeline that does this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It deploys the service.&lt;/li&gt;
&lt;li&gt;It uses AWS Fault Injection Service (FIS) to stop one running task on purpose.&lt;/li&gt;
&lt;li&gt;It waits, then checks that the service is back to its desired task count and that the load balancer shows all targets healthy.&lt;/li&gt;
&lt;li&gt;If recovery takes longer than the time you set, the stage fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You will build it twice. First by hand in the AWS console, so you see every part. Then with two CloudFormation stacks.&lt;/p&gt;

&lt;p&gt;The demo service runs plain nginx, so the test will pass. The demo shows how the pieces work. The test becomes useful when you point it at a real service.&lt;/p&gt;

&lt;p&gt;What this test proves: the service recovers from a lost task within a time limit that you choose. What it does not prove: zero failed requests during the failure. It does not send traffic while the task is down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tvcphwxkah8tgio3m7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tvcphwxkah8tgio3m7c.png" alt="Architecture Diagram" width="800" height="466"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fky26689yi9ikpdrq2abi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fky26689yi9ikpdrq2abi.png" alt="Resource Diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyzjq2ky5clpi5tpgr9c7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyzjq2ky5clpi5tpgr9c7.png" alt="Pipeline Diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An S3 bucket holds &lt;code&gt;source.zip&lt;/code&gt;. A change to this file starts the pipeline.&lt;/li&gt;
&lt;li&gt;CodePipeline has three stages: Source, Deploy, FailoverTest.&lt;/li&gt;
&lt;li&gt;The Deploy stage updates the ECS service.&lt;/li&gt;
&lt;li&gt;The FailoverTest stage runs a CodeBuild project. It starts the FIS experiment and checks recovery.&lt;/li&gt;
&lt;li&gt;The FIS experiment stops one task of &lt;code&gt;fis-demo-service&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A CloudWatch alarm watches the number of healthy targets. It is the experiment's stop condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The service runs 2 tasks. The experiment stops 1 of them, so one task stays up while ECS replaces the other.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the pieces fit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Deploy and FailoverTest are two different jobs.&lt;/strong&gt; The Deploy stage is a normal release. It puts the new version on ECS. The FailoverTest stage runs after it and asks one question: does the service still recover if a task dies? It works like an integration test that runs after a deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What FIS does.&lt;/strong&gt; FIS is the tool that breaks the task. It stops one task and only one, because the target is set to select a count of 1. It uses its own IAM role, which can only stop tasks in this cluster, so the pipeline does not need broad ECS permissions to break things. It also keeps a record of each experiment.&lt;/p&gt;

&lt;p&gt;You could stop a task with &lt;code&gt;aws ecs stop-task&lt;/code&gt; and get the same failure. FIS adds the limited role, the record, and the stop condition. FIS is worth more with faults that run over time, such as CPU stress or network delay, because a stop condition can halt those. For this one action, the stop-condition alarm cannot undo a stopped task. The real safety control is the count of 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why CodeBuild is in the pipeline.&lt;/strong&gt; At the time of writing, I found no built-in CodePipeline action that starts a FIS experiment. So the FailoverTest stage runs a CodeBuild project, and CodeBuild runs a script. CodeBuild does not break anything itself. It gives the script a place to run, and it turns the result into pass or fail for the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the script does.&lt;/strong&gt; CodeBuild reads &lt;code&gt;buildspec/run-fis-experiment.yml&lt;/code&gt; from &lt;code&gt;source.zip&lt;/code&gt;. That file runs &lt;code&gt;scripts/run-fis-experiment.sh&lt;/code&gt;. The script has four steps. It exits with an error if any step fails.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check before. It asks ECS how many tasks are running and asks the load balancer how many targets are healthy. Both must equal the desired count. If not, it stops. A service that was already broken should not be blamed on the test.&lt;/li&gt;
&lt;li&gt;Start. It runs &lt;code&gt;aws fis start-experiment&lt;/code&gt; with the experiment template ID. FIS stops one task.&lt;/li&gt;
&lt;li&gt;Wait for the experiment. It checks the status every 10 seconds until the experiment is &lt;code&gt;completed&lt;/code&gt;. If the status is &lt;code&gt;stopped&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt; or &lt;code&gt;cancelled&lt;/code&gt;, or it takes more than 300 seconds, the script fails.&lt;/li&gt;
&lt;li&gt;Wait for recovery. It keeps checking running tasks and healthy targets until both match the desired count again. If this takes longer than &lt;code&gt;RECOVERY_TIMEOUT&lt;/code&gt; (300 seconds by default), the script fails. Otherwise it prints &lt;code&gt;The service recovered. Test passed.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The script gets the template ID, cluster name, service name and target group ARN from environment variables on the CodeBuild project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why S3 is used.&lt;/strong&gt; The project uses two S3 buckets, for two different jobs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The source bucket holds &lt;code&gt;source.zip&lt;/code&gt;. A pipeline needs a place to read its source from. S3 keeps the demo simple, because it needs no GitHub connection and no extra setup step. CodePipeline reads the source as one zip file, and the bucket must have versioning turned on. A new upload of &lt;code&gt;source.zip&lt;/code&gt; creates a new version and starts a pipeline run. In a real project, you would replace this with a Git repository.&lt;/li&gt;
&lt;li&gt;The artifacts bucket belongs to the pipeline. CodePipeline uses it to pass files from one stage to the next. It is a separate bucket from the source bucket, and every pipeline needs one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When it runs.&lt;/strong&gt; The test runs on every pipeline run, after the Deploy stage succeeds. If Deploy fails, the test does not run. In this demo, a new &lt;code&gt;source.zip&lt;/code&gt; in the S3 bucket starts the pipeline. In real use, point the test at a staging environment first. Stopping a task in production on every release is a decision your team should make on purpose.&lt;/p&gt;
&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account and permission to create the resources below.&lt;/li&gt;
&lt;li&gt;AWS CLI v2 on your computer, signed in to your account.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zip&lt;/code&gt; on your computer.&lt;/li&gt;
&lt;li&gt;A Region where FIS, ECS Fargate, CodePipeline and CodeBuild are available. Use the same Region for every step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost: FIS charges per action-minute. At the time of writing, the price is $0.10 per action-minute. You also pay for the Fargate tasks, the load balancer and CodeBuild while they run. Delete everything when you finish (see Teardown).&lt;/p&gt;

&lt;p&gt;Use these names exactly. The scripts and templates depend on them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ECS cluster&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-cluster&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-service&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task definition family&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-alb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-tg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB security group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-alb-sg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task security group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-task-sg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch alarm&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-no-healthy-hosts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIS IAM role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-experiment-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIS experiment template name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-stop-one-task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source bucket&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeBuild project&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-failover-test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeBuild IAM role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-codebuild-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fis-demo-pipeline&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Get the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Gbengard/aws-fis-failover-testing.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aws-fis-failover-testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where to watch what happens
&lt;/h2&gt;

&lt;p&gt;Open these pages in separate tabs before you start the test. Refresh them as the pipeline runs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to see&lt;/th&gt;
&lt;th&gt;Where to look&lt;/th&gt;
&lt;th&gt;What you should see&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline progress&lt;/td&gt;
&lt;td&gt;CodePipeline console, &lt;code&gt;fis-demo-pipeline&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The three stages turn green one after another.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The test output&lt;/td&gt;
&lt;td&gt;CodeBuild console, &lt;code&gt;fis-demo-failover-test&lt;/code&gt;, then the latest build and its logs&lt;/td&gt;
&lt;td&gt;The log lines shown in the guide.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The experiment&lt;/td&gt;
&lt;td&gt;FIS console, &lt;strong&gt;Experiments&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;A new experiment. Its state goes from initiating to running to completed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The task being stopped&lt;/td&gt;
&lt;td&gt;ECS console, &lt;code&gt;fis-demo-cluster&lt;/code&gt;, &lt;code&gt;fis-demo-service&lt;/code&gt;, &lt;strong&gt;Tasks&lt;/strong&gt; tab. Use the filter to show stopped tasks too.&lt;/td&gt;
&lt;td&gt;One task stopped and a new one starting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What ECS did about it&lt;/td&gt;
&lt;td&gt;The service events in the same service page&lt;/td&gt;
&lt;td&gt;Messages about stopping and starting tasks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target health&lt;/td&gt;
&lt;td&gt;EC2 console, &lt;strong&gt;Target Groups&lt;/strong&gt;, &lt;code&gt;fis-demo-tg&lt;/code&gt;, &lt;strong&gt;Targets&lt;/strong&gt; tab&lt;/td&gt;
&lt;td&gt;One IP address goes unhealthy or draining. A new IP appears and turns healthy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stop condition&lt;/td&gt;
&lt;td&gt;CloudWatch console, &lt;strong&gt;Alarms&lt;/strong&gt;, &lt;code&gt;fis-demo-no-healthy-hosts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The state stays &lt;code&gt;OK&lt;/code&gt;. If it goes to &lt;code&gt;In alarm&lt;/code&gt;, no healthy target was left.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The new release&lt;/td&gt;
&lt;td&gt;ECS console, the service page, &lt;strong&gt;Deployments&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;A new task definition revision for the latest deploy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container logs&lt;/td&gt;
&lt;td&gt;CloudWatch console, &lt;strong&gt;Log groups&lt;/strong&gt;, &lt;code&gt;/ecs/fis-demo&lt;/code&gt; (automated path)&lt;/td&gt;
&lt;td&gt;nginx log lines from the running tasks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The app itself&lt;/td&gt;
&lt;td&gt;The ALB URL in your browser&lt;/td&gt;
&lt;td&gt;The nginx page keeps answering, because one task stays up.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Manual approach in the AWS console
&lt;/h2&gt;

&lt;p&gt;The manual path uses the default VPC. The CloudFormation path creates its own VPC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1. Build the service
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Create the security groups.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the EC2 console. In the left menu, choose &lt;strong&gt;Security Groups&lt;/strong&gt;, then choose &lt;strong&gt;Create security group&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Security group name: &lt;code&gt;fis-demo-alb-sg&lt;/code&gt;. Description: &lt;code&gt;ALB for the FIS demo&lt;/code&gt;. VPC: the default VPC.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Inbound rules&lt;/strong&gt;, choose &lt;strong&gt;Add rule&lt;/strong&gt;. Type: &lt;strong&gt;HTTP&lt;/strong&gt;. Source: &lt;strong&gt;Anywhere-IPv4&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create security group&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuykaqpdgd9bdr6ajy1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuykaqpdgd9bdr6ajy1w.png" alt="ALB Security Group" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Create security group&lt;/strong&gt; again. Name: &lt;code&gt;fis-demo-task-sg&lt;/code&gt;. Description: &lt;code&gt;Tasks for the FIS demo&lt;/code&gt;. VPC: the default VPC.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Inbound rules&lt;/strong&gt;, choose &lt;strong&gt;Add rule&lt;/strong&gt;. Type: &lt;strong&gt;HTTP&lt;/strong&gt;. Source: &lt;strong&gt;Custom&lt;/strong&gt;, then pick &lt;code&gt;fis-demo-alb-sg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create security group&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7yvlxhbk6nfnos4rircr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7yvlxhbk6nfnos4rircr.png" alt="FIS demo task sg" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Create the target group.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the EC2 console, choose &lt;strong&gt;Target Groups&lt;/strong&gt;, then &lt;strong&gt;Create target group&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Target type: &lt;strong&gt;IP addresses&lt;/strong&gt;. Target group name: &lt;code&gt;fis-demo-tg&lt;/code&gt;. Protocol: &lt;strong&gt;HTTP&lt;/strong&gt;. Port: &lt;code&gt;80&lt;/code&gt;. VPC: the default VPC.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Health checks&lt;/strong&gt;, keep the path as &lt;code&gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;.  Do not register any targets. Choose &lt;strong&gt;Next&lt;/strong&gt;. Choose &lt;strong&gt;Create target group&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Create the load balancer.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the EC2 console, choose &lt;strong&gt;Load Balancers&lt;/strong&gt;, then &lt;strong&gt;Create load balancer&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Application Load Balancer&lt;/strong&gt;, choose &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Load balancer name: &lt;code&gt;fis-demo-alb&lt;/code&gt;. Scheme: &lt;strong&gt;Internet-facing&lt;/strong&gt;. VPC: the default VPC.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Network Mappings&lt;/strong&gt;, select at least two Availability Zones.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Security groups&lt;/strong&gt;, remove the default group and select &lt;code&gt;fis-demo-alb-sg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Listeners and routing&lt;/strong&gt;, keep &lt;strong&gt;HTTP&lt;/strong&gt; and port &lt;code&gt;80&lt;/code&gt;. For &lt;strong&gt;Default action&lt;/strong&gt;, select &lt;code&gt;fis-demo-tg&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxzjaumware0h0krujcp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxzjaumware0h0krujcp.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Create load balancer&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 4. Create the cluster.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the ECS console. Choose &lt;strong&gt;Clusters&lt;/strong&gt;, then &lt;strong&gt;Create cluster&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cluster name: &lt;code&gt;fis-demo-cluster&lt;/code&gt;. Keep &lt;strong&gt;AWS Fargate (serverless)&lt;/strong&gt; selected.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 5. Create the task execution role.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ECS uses this role to pull the image and write logs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the IAM console. Choose &lt;strong&gt;Roles&lt;/strong&gt;, then &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;Custom trust policy&lt;/strong&gt;. Paste this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs-tasks.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. In the permissions list, search for &lt;code&gt;AmazonECSTaskExecutionRolePolicy&lt;/code&gt; and select it. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Role name: &lt;code&gt;fis-demo-task-execution-role&lt;/code&gt;. Choose &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcw1s06ajtjofxo8gxiry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcw1s06ajtjofxo8gxiry.png" alt=" " width="799" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6. Create the task definition.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the ECS console, choose &lt;strong&gt;Task definitions&lt;/strong&gt;, then &lt;strong&gt;Create new task definition&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Task definition family: &lt;code&gt;fis-demo-task&lt;/code&gt;. Launch type: &lt;strong&gt;AWS Fargate&lt;/strong&gt;. CPU: &lt;strong&gt;.25 vCPU&lt;/strong&gt;. Memory: &lt;strong&gt;.5 GB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Task execution role: choose &lt;code&gt;fis-demo-task-execution-role&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Container - 1&lt;/strong&gt;, set Name: &lt;code&gt;web&lt;/code&gt;. Image URI: &lt;code&gt;public.ecr.aws/docker/library/nginx:latest&lt;/code&gt;. Container port: &lt;code&gt;80&lt;/code&gt;. Protocol: &lt;strong&gt;TCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 7. Create the service.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;fis-demo-cluster&lt;/code&gt;. On the &lt;strong&gt;Services&lt;/strong&gt; tab, choose &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set Family: &lt;code&gt;fis-demo-task&lt;/code&gt; (latest revision). Service name: &lt;code&gt;fis-demo-service&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Compute options&lt;/strong&gt;, choose &lt;strong&gt;Launch type&lt;/strong&gt;, then &lt;strong&gt;FARGATE&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Deployment configuration&lt;/strong&gt;, set Desired tasks: &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Networking&lt;/strong&gt;, keep the default VPC and subnets. Remove the default security group and select &lt;code&gt;fis-demo-task-sg&lt;/code&gt;. Turn &lt;strong&gt;Public IP&lt;/strong&gt; on.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Load balancing&lt;/strong&gt;, check &lt;strong&gt;use load balancing&lt;/strong&gt;, choose &lt;strong&gt;Application Load Balancer&lt;/strong&gt;. Choose &lt;strong&gt;Use an existing load balancer&lt;/strong&gt; and select &lt;code&gt;fis-demo-alb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For the container, choose &lt;code&gt;web 80:80&lt;/code&gt;. Choose &lt;strong&gt;Use an existing listener&lt;/strong&gt; (&lt;code&gt;HTTP:80&lt;/code&gt;) and &lt;strong&gt;Use an existing target group&lt;/strong&gt; (&lt;code&gt;fis-demo-tg&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 8. Check the service.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wait until the service shows 2 running tasks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F18ruf5fqv8wr18n8qjdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F18ruf5fqv8wr18n8qjdi.png" alt="services running" width="799" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the EC2 console, open &lt;code&gt;fis-demo-tg&lt;/code&gt;. On the &lt;strong&gt;Targets&lt;/strong&gt; tab, wait until both targets are &lt;strong&gt;healthy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwclbgr7rumgptjyvsdy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwclbgr7rumgptjyvsdy.png" alt="Healthy targets" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the ALB DNS name in your browser, make sure you open with http. You should see the nginx welcome page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyauf575etz8eheqo7k93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyauf575etz8eheqo7k93.png" alt="Nginx welcome page" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2. Add the FIS experiment
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 9. Create the CloudWatch alarm.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the CloudWatch console. Choose &lt;strong&gt;Alarms&lt;/strong&gt;, then &lt;strong&gt;Create alarm&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Select metric&lt;/strong&gt;. Choose &lt;strong&gt;ApplicationELB&lt;/strong&gt;, then &lt;strong&gt;Per AppELB, per TG Metrics&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;HealthyHostCount&lt;/strong&gt; for &lt;code&gt;fis-demo-tg&lt;/code&gt;. Choose &lt;strong&gt;Select metric&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Statistic: &lt;strong&gt;Minimum&lt;/strong&gt;. Period: &lt;strong&gt;1 minute&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Conditions: &lt;strong&gt;Static&lt;/strong&gt;, &lt;strong&gt;Lower&lt;/strong&gt;, than &lt;code&gt;1&lt;/code&gt;. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Remove any notification action. Choose &lt;strong&gt;Next&lt;/strong&gt;, click on the prompt message &lt;strong&gt;proceed without actions&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu6u97m799g72m33yqwzn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu6u97m799g72m33yqwzn.png" alt="Cloudwatch alarm preview" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Alarm name: &lt;code&gt;fis-demo-no-healthy-hosts&lt;/code&gt;. Choose &lt;strong&gt;Next&lt;/strong&gt;, then &lt;strong&gt;Create alarm&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 10. Create the IAM role for FIS.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the IAM console. Choose &lt;strong&gt;Roles&lt;/strong&gt;, then &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;Custom trust policy&lt;/strong&gt;. Paste this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fis.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. Do not attach any policy. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Role name: &lt;code&gt;fis-demo-experiment-role&lt;/code&gt;. Choose &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the role. Choose &lt;strong&gt;Add permissions&lt;/strong&gt;, then &lt;strong&gt;Create inline policy&lt;/strong&gt;. Choose the &lt;strong&gt;JSON&lt;/strong&gt; tab and paste this. Replace &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ecs:DescribeTasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs:ListTasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tag:GetResources"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs:StopTask"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ArnEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"ecs:cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ecs:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:cluster/fis-demo-cluster"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. Policy name: &lt;code&gt;fis-demo-stop-task&lt;/code&gt;. Choose &lt;strong&gt;Create policy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 11. Create the experiment template.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the FIS console. Choose &lt;strong&gt;Experiment templates&lt;/strong&gt;, then &lt;strong&gt;Create experiment template&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Description: &lt;code&gt;Stop one running task of the demo ECS service&lt;/code&gt;. Name: &lt;code&gt;fis-demo-stop-one-task&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Actions&lt;/strong&gt;, choose &lt;strong&gt;Add action&lt;/strong&gt;. Name: &lt;code&gt;StopOneTask&lt;/code&gt;. Action type: &lt;code&gt;aws:ecs:stop-task&lt;/code&gt;. Choose &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgupvpt84omev1otnw5l5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgupvpt84omev1otnw5l5.png" alt="Stop One Task" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Targets&lt;/strong&gt;, edit the target that FIS created for the action. Name: &lt;code&gt;OneTask&lt;/code&gt;. Resource type: &lt;code&gt;aws:ecs:task&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the target to use resource parameters: &lt;code&gt;cluster&lt;/code&gt; = &lt;code&gt;fis-demo-cluster&lt;/code&gt; and &lt;code&gt;service&lt;/code&gt; = &lt;code&gt;fis-demo-service&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Selection mode: &lt;strong&gt;Count&lt;/strong&gt;. Number of resources: &lt;code&gt;1&lt;/code&gt;. Choose &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1mnr0ohv820cm24qfjg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1mnr0ohv820cm24qfjg.png" alt="Onetask" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on &lt;strong&gt;next&lt;/strong&gt;. Under &lt;strong&gt;Service access&lt;/strong&gt;, choose &lt;strong&gt;Use an existing IAM role&lt;/strong&gt; and select &lt;code&gt;fis-demo-experiment-role&lt;/code&gt; and click &lt;strong&gt;next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Stop conditions&lt;/strong&gt;, select the alarm &lt;code&gt;fis-demo-no-healthy-hosts&lt;/code&gt;and click &lt;strong&gt;next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create experiment template&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feobzu1uq84tmbij1mqpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feobzu1uq84tmbij1mqpa.png" alt="FIS experiment template" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 12. Run the experiment by hand.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the template. Choose &lt;strong&gt;Start experiment&lt;/strong&gt;. Type &lt;code&gt;start&lt;/code&gt; when asked, then choose &lt;strong&gt;Start experiment&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Watch the experiment state change until it shows &lt;strong&gt;Completed&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F29em6ivq8k0gzybtzcuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F29em6ivq8k0gzybtzcuv.png" alt="FIS Experiment Completed" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the ECS console, open &lt;code&gt;fis-demo-service&lt;/code&gt;. On the &lt;strong&gt;Tasks&lt;/strong&gt; tab, you see one task stopped and a new task starting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiyhdog85gvt6atxhq56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiyhdog85gvt6atxhq56.png" alt="task running" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the target group, wait until both targets are &lt;strong&gt;healthy&lt;/strong&gt; again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The service recovered. Now put this check into a pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 3. Add the pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 13. Create the buckets.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the S3 console. Choose &lt;strong&gt;Create bucket&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bucket name: &lt;code&gt;fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Bucket Versioning&lt;/strong&gt;, choose &lt;strong&gt;Enable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create bucket&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create bucket&lt;/strong&gt; again. Bucket name: &lt;code&gt;fis-demo-artifacts-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt;. Leave versioning off. Choose &lt;strong&gt;Create bucket&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The source bucket holds &lt;code&gt;source.zip&lt;/code&gt;. The pipeline keeps its working files in the artifacts bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 14. Upload the source.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From the repo folder, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash tools/package-source.sh fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This builds &lt;code&gt;source.zip&lt;/code&gt; and uploads it. The zip has three files: &lt;code&gt;imagedefinitions.json&lt;/code&gt;, &lt;code&gt;buildspec/run-fis-experiment.yml&lt;/code&gt; and &lt;code&gt;scripts/run-fis-experiment.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 15. Create the CodeBuild role.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the IAM console. Choose &lt;strong&gt;Roles&lt;/strong&gt;, then &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;Custom trust policy&lt;/strong&gt;. Paste this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"codebuild.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. Do not attach any policy. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Role name: &lt;code&gt;fis-demo-codebuild-role&lt;/code&gt;. Choose &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the role. Choose &lt;strong&gt;Add permissions&lt;/strong&gt;, then &lt;strong&gt;Create inline policy&lt;/strong&gt;. Choose the &lt;strong&gt;JSON&lt;/strong&gt; tab and paste this. Replace &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;TEMPLATE_ID&amp;gt;&lt;/code&gt;. You get the template ID from the FIS console, on the experiment template page.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:log-group:/aws/codebuild/fis-demo-failover-test*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObjectVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::fis-demo-artifacts-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs:DescribeServices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"elasticloadbalancing:DescribeTargetHealth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fis:StartExperiment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:fis:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:experiment-template/&amp;lt;TEMPLATE_ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:fis:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:experiment/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fis:GetExperiment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:fis:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:experiment/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;ACCOUNT_ID&amp;gt;:role/fis-demo-experiment-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"iam:PassedToService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fis.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:CreateServiceLinkedRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"iam:AWSServiceName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fis.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. Policy name: &lt;code&gt;fis-demo-codebuild-policy&lt;/code&gt;. Choose &lt;strong&gt;Create policy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 16. Create the pipeline role.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the IAM console. Choose &lt;strong&gt;Roles&lt;/strong&gt;, then &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;Custom trust policy&lt;/strong&gt;. Paste this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"codepipeline.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. Do not attach any policy. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Role name: &lt;code&gt;fis-demo-pipeline-role&lt;/code&gt;. Choose &lt;strong&gt;Create role&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the role. Choose &lt;strong&gt;Add permissions&lt;/strong&gt;, then &lt;strong&gt;Create inline policy&lt;/strong&gt;. Choose the &lt;strong&gt;JSON&lt;/strong&gt; tab and paste this. Replace &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObjectVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketVersioning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketLocation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::fis-demo-artifacts-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::fis-demo-artifacts-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"codebuild:StartBuild"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"codebuild:BatchGetBuilds"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:codebuild:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:project/fis-demo-failover-test"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecs:DescribeServices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecs:DescribeTaskDefinition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecs:DescribeTasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecs:ListTasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecs:RegisterTaskDefinition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecs:UpdateService"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;ACCOUNT_ID&amp;gt;:role/fis-demo-task-execution-role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"iam:PassedToService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs-tasks.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;. Policy name: &lt;code&gt;fis-demo-pipeline-policy&lt;/code&gt;. Choose &lt;strong&gt;Create policy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 17. Create the CodeBuild project.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the CodeBuild console. Choose &lt;strong&gt;Create project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Project name: &lt;code&gt;fis-demo-failover-test&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Source&lt;/strong&gt;, set Source provider to &lt;strong&gt;Amazon S3&lt;/strong&gt;. The console does not list CodePipeline here. Set Bucket to &lt;code&gt;fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt; and S3 object key to &lt;code&gt;source.zip&lt;/code&gt;. When you add this project to the pipeline in Step 18, CodePipeline changes the project's source to CodePipeline for you.&lt;/li&gt;
&lt;li&gt;Environment: &lt;strong&gt;Managed image&lt;/strong&gt;. Use the latest Standard image that the console offers for Ubuntu.&lt;/li&gt;
&lt;li&gt;Service role: &lt;strong&gt;Existing service role&lt;/strong&gt;. Select &lt;code&gt;fis-demo-codebuild-role&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Buildspec&lt;/strong&gt;, choose &lt;strong&gt;Use a buildspec file&lt;/strong&gt;. Buildspec name: &lt;code&gt;buildspec/run-fis-experiment.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Additional configuration&lt;/strong&gt;, add four environment variables (type &lt;strong&gt;Plaintext&lt;/strong&gt;):

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TEMPLATE_ID&lt;/code&gt;: the experiment template ID from the FIS console.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER_NAME&lt;/code&gt;: &lt;code&gt;fis-demo-cluster&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SERVICE_NAME&lt;/code&gt;: &lt;code&gt;fis-demo-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TARGET_GROUP_ARN&lt;/code&gt;: the full ARN of &lt;code&gt;fis-demo-tg&lt;/code&gt;. Copy it from the target group's details page in the EC2 console. It starts with &lt;code&gt;arn:aws:elasticloadbalancing&lt;/code&gt;. Do not use the name.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Create build project&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 18. Create the pipeline.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the CodePipeline console. Choose &lt;strong&gt;Create pipeline&lt;/strong&gt;. Choose the option to build a custom pipeline, then &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Pipeline name: &lt;code&gt;fis-demo-pipeline&lt;/code&gt;. Service role: &lt;strong&gt;Existing service role&lt;/strong&gt;. Select &lt;code&gt;fis-demo-pipeline-role&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Advanced settings&lt;/strong&gt;, set the artifact store to &lt;strong&gt;Custom location&lt;/strong&gt; and select &lt;code&gt;fis-demo-artifacts-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt;. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Source provider: &lt;strong&gt;Amazon S3&lt;/strong&gt;. Bucket: your source bucket. S3 object key: &lt;code&gt;source.zip&lt;/code&gt;. Output artifact format: &lt;strong&gt;CodePipeline default&lt;/strong&gt;. Choose &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Skip the build stage. Choose &lt;strong&gt;Skip build stage&lt;/strong&gt; and Choose &lt;strong&gt;Skip test stage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Deploy provider: &lt;strong&gt;Amazon ECS&lt;/strong&gt;. Cluster name: &lt;code&gt;fis-demo-cluster&lt;/code&gt;. Service name: &lt;code&gt;fis-demo-service&lt;/code&gt;. Image definitions file: &lt;code&gt;imagedefinitions.json&lt;/code&gt;. Choose &lt;strong&gt;Next&lt;/strong&gt;, then &lt;strong&gt;Create pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Edit&lt;/strong&gt;. After the Deploy stage, choose &lt;strong&gt;Add stage&lt;/strong&gt;. Stage name: &lt;code&gt;FailoverTest&lt;/code&gt;. Choose &lt;strong&gt;Add action group&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Action name: &lt;code&gt;RunFisExperiment&lt;/code&gt;. Action provider: &lt;strong&gt;AWS CodeBuild&lt;/strong&gt;. Input artifacts: &lt;code&gt;SourceArtifact&lt;/code&gt;. Project name: &lt;code&gt;fis-demo-failover-test&lt;/code&gt;. Choose &lt;strong&gt;Done&lt;/strong&gt;, then &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 19. Run it.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Release change&lt;/strong&gt; on the pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vrx08j43ldbn6tkv6aj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vrx08j43ldbn6tkv6aj.png" alt="Codepipeline running" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg6h3m7ff6f2k52a30m0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg6h3m7ff6f2k52a30m0y.png" alt="ECS deployment" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8zmi4rxgjk4c8zptua9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8zmi4rxgjk4c8zptua9v.png" alt="ECS tasks" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Watch the three stages. When FailoverTest succeeds, open the build logs. A passing run looks like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: check the service is healthy before the test.
desired=2 running=2 healthy_targets=2
Step 2: start the experiment from template EXT2BR9MPSB8Hdix.
Experiment ID: EXPKoHXWhUDcXadYBt
Step 3: wait for the experiment to end.
experiment status: initiating
experiment status: running
experiment status: completed
Step 4: wait for the service to recover.
desired=2 running=1 healthy_targets=2
desired=2 running=1 healthy_targets=2
desired=2 running=1 healthy_targets=2
desired=2 running=1 healthy_targets=1
desired=2 running=1 healthy_targets=1
desired=2 running=2 healthy_targets=2
The service recovered. Test passed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1 shows a healthy service: 2 tasks running and 2 healthy targets.&lt;/li&gt;
&lt;li&gt;In Step 3, FIS stops one task. The experiment goes from &lt;code&gt;initiating&lt;/code&gt; to &lt;code&gt;running&lt;/code&gt; to &lt;code&gt;completed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In Step 4, the running count drops to 1 while ECS starts a replacement.&lt;/li&gt;
&lt;li&gt;The healthy target count can lag behind the running count. In this run it stayed at 2 for a few checks, then dropped to 1. That is why the script checks both numbers and not only the healthy targets.&lt;/li&gt;
&lt;li&gt;When both numbers are back at 2, the test passes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this run, the whole stage took about 100 seconds. Your IDs and timing will differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teardown for the manual approach
&lt;/h2&gt;

&lt;p&gt;Delete in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CodePipeline: choose &lt;code&gt;fis-demo-pipeline&lt;/code&gt;, then &lt;strong&gt;Delete pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;CodeBuild: choose &lt;code&gt;fis-demo-failover-test&lt;/code&gt;, then &lt;strong&gt;Delete build project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;ECS: open &lt;code&gt;fis-demo-cluster&lt;/code&gt;, choose &lt;code&gt;fis-demo-service&lt;/code&gt;, then &lt;strong&gt;Delete service&lt;/strong&gt;. Turn on &lt;strong&gt;Force delete&lt;/strong&gt; if asked. Then choose &lt;strong&gt;Delete cluster&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;FIS: delete the experiment template &lt;code&gt;fis-demo-stop-one-task&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CloudWatch: delete the alarm &lt;code&gt;fis-demo-no-healthy-hosts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;IAM: delete the four roles &lt;code&gt;fis-demo-experiment-role&lt;/code&gt;, &lt;code&gt;fis-demo-codebuild-role&lt;/code&gt;, &lt;code&gt;fis-demo-pipeline-role&lt;/code&gt; and &lt;code&gt;fis-demo-task-execution-role&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;S3: empty and delete the source bucket and the artifacts bucket (&lt;code&gt;fis-demo-source-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt; and &lt;code&gt;fis-demo-artifacts-&amp;lt;ACCOUNT_ID&amp;gt;-&amp;lt;REGION&amp;gt;&lt;/code&gt;). The source bucket is versioned, so use &lt;code&gt;bash tools/empty-bucket.sh &amp;lt;bucket-name&amp;gt;&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;ECS: open &lt;strong&gt;Task definitions&lt;/strong&gt; and choose &lt;code&gt;fis-demo-task&lt;/code&gt;. Select all revisions and deregister them. Then select all revisions again and delete them. Do this after the service is deleted. A deleted revision stays in &lt;code&gt;DELETE_IN_PROGRESS&lt;/code&gt; until every task and service that uses it has ended. The pipeline's Deploy stage registers new revisions, so there can be more than one.&lt;/li&gt;
&lt;li&gt;CloudWatch: open &lt;strong&gt;Log groups&lt;/strong&gt;, click on &lt;strong&gt;Log Management&lt;/strong&gt; and delete &lt;code&gt;/aws/codebuild/fis-demo-failover-test&lt;/code&gt;. CodeBuild creates it by itself on the first run. Also delete the log group that ECS created for the task, which should be &lt;code&gt;/ecs/fis-demo-task&lt;/code&gt;. Check the real name in the list.&lt;/li&gt;
&lt;li&gt;EC2: delete the load balancer &lt;code&gt;fis-demo-alb&lt;/code&gt;, then the target group &lt;code&gt;fis-demo-tg&lt;/code&gt;, then the security groups &lt;code&gt;fis-demo-task-sg&lt;/code&gt; and &lt;code&gt;fis-demo-alb-sg&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To do steps 9 and 10 from the command line, run &lt;code&gt;bash tools/cleanup-leftovers.sh&lt;/code&gt;. It handles the known names above.&lt;/p&gt;

&lt;p&gt;Do this before you start the automated approach. Both approaches use the same resource names, so leftover resources will cause name conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated approach with CloudFormation
&lt;/h2&gt;

&lt;p&gt;The automated path uses two stacks. The pipeline stack must wait, because the pipeline needs &lt;code&gt;source.zip&lt;/code&gt; in the source bucket when it is created. That upload is a manual step, so it sits between the two stacks.&lt;/p&gt;

&lt;p&gt;Template &lt;code&gt;01-base-stack.yaml&lt;/code&gt; creates the VPC, the ALB, the ECS service, the source bucket, and the FIS parts: the alarm, the IAM role and the experiment template.&lt;/p&gt;

&lt;p&gt;Template &lt;code&gt;02-pipeline-stack.yaml&lt;/code&gt; creates the artifact bucket, the CodeBuild project, the IAM roles and the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Deploy the base stack.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; cloudformation/01-base-stack.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-base &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it finishes, print the outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-base &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Stacks[0].Outputs"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;AlbUrl&lt;/code&gt; in your browser. You should see the nginx welcome page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Upload the source.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BUCKET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-base &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Stacks[0].Outputs[?OutputKey=='SourceBucketName'].OutputValue"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
bash tools/package-source.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3. Deploy the pipeline stack.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; cloudformation/02-pipeline-stack.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-pipeline &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline starts by itself. In the CodePipeline console, open &lt;code&gt;fis-demo-pipeline&lt;/code&gt; and watch the stages. When FailoverTest succeeds, open the CodeBuild logs. You should see &lt;code&gt;The service recovered. Test passed.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4. See what the test does.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the CodeBuild log of the FailoverTest stage. A passing run looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: check the service is healthy before the test.
desired=2 running=2 healthy_targets=2
Step 2: start the experiment from template EXT2BR9MPSB8Hdix.
Experiment ID: EXPKoHXWhUDcXadYBt
Step 3: wait for the experiment to end.
experiment status: initiating
experiment status: running
experiment status: completed
Step 4: wait for the service to recover.
desired=2 running=1 healthy_targets=2
desired=2 running=1 healthy_targets=2
desired=2 running=1 healthy_targets=2
desired=2 running=1 healthy_targets=1
desired=2 running=1 healthy_targets=1
desired=2 running=2 healthy_targets=2
The service recovered. Test passed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1 shows a healthy service: 2 tasks running and 2 healthy targets.&lt;/li&gt;
&lt;li&gt;In Step 3, FIS stops one task. The experiment goes from &lt;code&gt;initiating&lt;/code&gt; to &lt;code&gt;running&lt;/code&gt; to &lt;code&gt;completed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In Step 4, the running count drops to 1 while ECS starts a replacement.&lt;/li&gt;
&lt;li&gt;The healthy target count can lag behind the running count. In this run it stayed at 2 for a few checks, then dropped to 1. That is why the script checks both numbers and not only the healthy targets.&lt;/li&gt;
&lt;li&gt;When both numbers are back at 2, the test passes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this run, the whole stage took about 100 seconds. Your IDs and timing will differ.&lt;/p&gt;

&lt;p&gt;To change how long the test waits for recovery, set the environment variable &lt;code&gt;RECOVERY_TIMEOUT&lt;/code&gt; (in seconds) on the CodeBuild project. The default is 300. This is your real pass or fail line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teardown for the automated approach
&lt;/h2&gt;

&lt;p&gt;Delete the pipeline stack first. The base stack exports values that it uses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-caller-identity &lt;span class="nt"&gt;--query&lt;/span&gt; Account &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws configure get region&lt;span class="si"&gt;)&lt;/span&gt;

aws s3 &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"s3://fis-demo-artifacts-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-pipeline &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
aws cloudformation &lt;span class="nb"&gt;wait &lt;/span&gt;stack-delete-complete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-pipeline &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
bash tools/empty-bucket.sh &lt;span class="s2"&gt;"fis-demo-source-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-base &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
aws cloudformation &lt;span class="nb"&gt;wait &lt;/span&gt;stack-delete-complete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fis-demo-base &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
bash tools/cleanup-leftovers.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; at the end of each line stops the chain at the first failure. That way the base stack is not deleted while its source bucket still has files in it.&lt;/p&gt;

&lt;p&gt;The last line cleans up what the stacks do not own. The Deploy stage registers new task definition revisions on its own, and CodeBuild creates its own log group on the first run. Neither belongs to a stack, so deleting the stacks leaves them behind. The script deregisters and deletes every revision of &lt;code&gt;fis-demo-task&lt;/code&gt; and deletes the log groups. It runs last because a revision cannot finish deleting while a service still uses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The experiment fails right after it starts.&lt;/strong&gt;&lt;br&gt;
FIS picks targets when the experiment starts. If it finds no matching tasks, the experiment fails. Check that the service has running tasks and that the cluster and service names in the target are exact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The experiment stops with an access error on &lt;code&gt;ecs:StopTask&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
The FIS role needs &lt;code&gt;ecs:StopTask&lt;/code&gt;. In the policy, the &lt;code&gt;ecs:cluster&lt;/code&gt; condition must match your cluster ARN. To check if the condition is the cause, remove it and run the experiment again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Source stage fails.&lt;/strong&gt;&lt;br&gt;
The pipeline reads &lt;code&gt;source.zip&lt;/code&gt; from the source bucket. Check that the file is there and that versioning is on for the bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FailoverTest fails with "The service is not healthy before the test."&lt;/strong&gt;&lt;br&gt;
The script checks the service before it breaks anything. Wait until the Deploy stage has finished and both targets are healthy, then run the stage again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FailoverTest fails with "is not a valid target group ARN".&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;TARGET_GROUP_ARN&lt;/code&gt; variable holds the target group name, not its ARN. Edit the CodeBuild project, open &lt;strong&gt;Additional configuration&lt;/strong&gt;, and paste the full ARN. The script now checks this at the start and tells you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FailoverTest shows &lt;code&gt;desired=None running=None&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
ECS did not find the service. Check that &lt;code&gt;CLUSTER_NAME&lt;/code&gt; is &lt;code&gt;fis-demo-cluster&lt;/code&gt;, that &lt;code&gt;SERVICE_NAME&lt;/code&gt; is &lt;code&gt;fis-demo-service&lt;/code&gt;, and that the CodeBuild project is in the same Region as the service. To test from your computer, run &lt;code&gt;aws ecs describe-services --cluster fis-demo-cluster --services fis-demo-service --query "services[0].[runningCount,desiredCount]"&lt;/code&gt;. The script now stops with a clear message in this case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FailoverTest fails with "not authorized to perform: iam:CreateServiceLinkedRole".&lt;/strong&gt;&lt;br&gt;
FIS uses a service-linked role named &lt;code&gt;AWSServiceRoleForFIS&lt;/code&gt;. If it does not exist in the account, FIS creates it when the first experiment starts, and the identity that starts the experiment needs permission to do that. Here, that identity is the CodeBuild role. Add &lt;code&gt;iam:CreateServiceLinkedRole&lt;/code&gt; to the CodeBuild role, with the condition &lt;code&gt;iam:AWSServiceName&lt;/code&gt; set to &lt;code&gt;fis.amazonaws.com&lt;/code&gt;. The policy in this guide includes it. The error can also appear on a later run if someone deleted the service-linked role in between, for example during cleanup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FailoverTest fails with "did not recover".&lt;/strong&gt;&lt;br&gt;
Recovery took longer than &lt;code&gt;RECOVERY_TIMEOUT&lt;/code&gt;. Open the ECS service events and the target group health to see why. Raise the timeout only if the longer time is acceptable for your service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The stack delete fails with "bucket is not empty".&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;bash tools/empty-bucket.sh &amp;lt;bucket-name&amp;gt;&lt;/code&gt; for the source bucket, or &lt;code&gt;aws s3 rm --recursive&lt;/code&gt; for the artifact bucket, and delete again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The base stack delete fails because an export is in use.&lt;/strong&gt;&lt;br&gt;
Delete the &lt;code&gt;fis-demo-pipeline&lt;/code&gt; stack first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have a pipeline that breaks one task on purpose and checks that the service comes back. The test is small, but it turns a design claim into a check that runs on every release.&lt;/p&gt;

&lt;p&gt;The full code is in the repo: &lt;a href="https://github.com/Gbengard/aws-fis-failover-testing" rel="noopener noreferrer"&gt;https://github.com/Gbengard/aws-fis-failover-testing&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cicd</category>
      <category>chaosengineering</category>
    </item>
    <item>
      <title>Zero-Downtime Blue-Green Deployment on ECS: Console Walkthrough + CloudFormation Automation</title>
      <dc:creator>Gbenga Ogbonyenitan</dc:creator>
      <pubDate>Sat, 10 Oct 2026 11:05:49 +0000</pubDate>
      <link>https://dev.to/gbengard/zero-downtime-blue-green-deployment-on-ecs-console-walkthrough-cloudformation-automation-3ba0</link>
      <guid>https://dev.to/gbengard/zero-downtime-blue-green-deployment-on-ecs-console-walkthrough-cloudformation-automation-3ba0</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;A normal deployment replaces your app a little at a time. If the new version has a bug, some real users hit it before anyone notices— monitoring catches it eventually, but "eventually" can be minutes of bad requests.&lt;/p&gt;

&lt;p&gt;Blue-green deployment fixes this differently. Instead of replacing the running app in place, you start the new version alongside the old one and test it privately before anyone else sees it. On its own, ECS's blue/green feature doesn't actually wait for you to do that checking — the moment the new version passes its health checks, it shifts all production traffic over automatically, whether or not anyone looked at it first.&lt;/p&gt;

&lt;p&gt;What turns &lt;strong&gt;"check it privately"&lt;/strong&gt; into an actual gate — a real point where nothing goes live until you say so — is one more piece this guide adds on top: a manual approval step. With it in place, the deployment stops right after the new version is reachable for checking, and waits for you to approve it before a single real user sees it. If it looks wrong, you reject it instead, and the new version never goes live at all.&lt;/p&gt;

&lt;p&gt;In this guide, you'll build that setup on AWS: a container running on ECS Fargate, deployed through a pipeline that builds your code, ships it to a new set of tasks, lets you check it privately behind a test rule, pauses there waiting for your approval, and only then shifts production traffic over — plus, once it's live, keeps the old version standing by for a few minutes in case you need to revert instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on how this is built:&lt;/strong&gt; This guide uses Amazon ECS's own native blue/green deployment feature, not AWS CodeDeploy. Up until July 2025, ECS blue/green deployments needed a separate CodeDeploy application, deployment group, and an &lt;code&gt;appspec.yaml&lt;/code&gt; file to coordinate the traffic shift. As of July 2025, ECS does all of that itself — you just set a deployment strategy on the service, point it at two target groups, and ECS handles the shift, the bake time, and rollback on its own. If you're following an older tutorial and can't find a "CodeDeploy" option in the ECS console anymore, this is why — it's been folded into ECS directly.&lt;/p&gt;

&lt;p&gt;You'll build it two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;By hand, in the AWS Console&lt;/strong&gt; — so you actually see how each piece connects to the next one, not just what a template produces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With CloudFormation&lt;/strong&gt; — the same architecture, deployed as one stack&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both end up at the same result. Go through the console version first if you're newer to this — it's slower, but it's the version that actually teaches you what's happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8tgguh4qd0j134qupwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8tgguh4qd0j134qupwz.png" alt="Overall architecture diagram of the blue-green deployment pipeline on ECS Fargate" width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx3axqqnbjs690jvhxea4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx3axqqnbjs690jvhxea4.png" alt="Diagram showing how the AWS resources in this project relate to each other" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The part that actually makes this zero-downtime is the two target groups and the two listener rules — both living on the same listener, port 80.&lt;/p&gt;

&lt;p&gt;The production rule catches ordinary traffic and always points at&lt;br&gt;
whichever target group is "live." The test rule only fires for requests carrying a specific header, so you can privately hit the new version before it goes live without needing a second port. ECS itself flips which target group each rule points to during a deployment — nothing gets exposed to real users until it's already been checked.&lt;/p&gt;

&lt;p&gt;(If you're used to the older CodeDeploy-based pattern, that one really did use a separate port for test traffic. Native ECS blue/green doesn't need that — the ECS "Create service" wizard only ever asks for one listener, then two rules under it, so a header is what tells the two apart instead of a port.)&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Before you start
&lt;/h2&gt;

&lt;p&gt;You'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account with permissions to create VPCs, ECS, IAM roles, ALBs, CodePipeline, and CodeBuild resources. All resources in this guide are created in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Docker installed locally, if you want to build and push the first image yourself before the pipeline takes over&lt;/li&gt;
&lt;li&gt;A GitHub account and a repo the pipeline can pull from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important — clone this repo first:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Gbengard/blue-green-ecs-deployment.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline needs the &lt;code&gt;pipeline-config/&lt;/code&gt; folder — containing&lt;br&gt;
&lt;code&gt;buildspec.yml&lt;/code&gt;, &lt;code&gt;Dockerfile&lt;/code&gt;, and &lt;code&gt;index.html&lt;/code&gt; — sitting in your own GitHub repo before it can run. Push the &lt;strong&gt;whole cloned project as-is&lt;/strong&gt;, keeping &lt;code&gt;pipeline-config/&lt;/code&gt; as a subfolder rather than flattening its contents into the repo root — Step 9 points CodeBuild at &lt;code&gt;pipeline-config/buildspec.yml&lt;/code&gt;, and the buildspec itself &lt;code&gt;cd&lt;/code&gt;s into &lt;code&gt;pipeline-config/&lt;/code&gt; to find the Dockerfile, so the folder needs to stay where it is. Without this step, the pipeline's Source stage has nothing to pull, and everything after it will fail before it even starts.&lt;/p&gt;

&lt;p&gt;Every resource in this guide has a fixed name, so it's easy to follow and easy to find again later. Keep this table open as you work through it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Gateway&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-igw&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public subnet A&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-public-a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public subnet B&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-public-b&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route table&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-public-rt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB security group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-alb-sg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service security group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-svc-sg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECR repository&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-alb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blue (primary) target group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green (alternate) target group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-tg-green&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production listener rule&lt;/td&gt;
&lt;td&gt;port 80, priority 2, path &lt;code&gt;/*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test listener rule&lt;/td&gt;
&lt;td&gt;same listener (port 80), priority 1, header &lt;code&gt;X-Bg-Test: true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS cluster&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-cluster&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS task definition family&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-service&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task execution role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-task-exec-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-task-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS infrastructure role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-ecs-infra-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeBuild project&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-build&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeBuild service role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-codebuild-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 artifact bucket&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-artifacts-&amp;lt;your-account-id&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodePipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-pipeline&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeStar GitHub connection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bluegreen-demo-github-connection&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch log group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ecs/bluegreen-demo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Manual Approach (AWS Console)
&lt;/h2&gt;

&lt;p&gt;Every step below tells you exactly which console page to open, what to click, what to type, and which button ends the step. Screenshots are included wherever they help.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1 — Create the VPC
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#Home:" rel="noopener noreferrer"&gt;&lt;strong&gt;VPC console&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create VPC&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;VPC only&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IPv4 CIDR: &lt;code&gt;10.20.0.0/16&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Leave everything else as default&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create VPC&lt;/strong&gt; at the bottom of the page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ls3yznk0h6kg0d5g9s7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ls3yznk0h6kg0d5g9s7.png" alt="VPC creation screen" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 — Create two public subnets
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In the left sidebar, click &lt;strong&gt;Subnets&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create subnet&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;VPC ID: choose &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Subnet 1 of 1&lt;/strong&gt;: name &lt;code&gt;bluegreen-demo-public-a&lt;/code&gt;, Availability
Zone: pick the first one in the list, IPv4 CIDR: &lt;code&gt;10.20.1.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add new subnet&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Subnet 2 of 2&lt;/strong&gt;: name &lt;code&gt;bluegreen-demo-public-b&lt;/code&gt;, Availability Zone: pick a &lt;strong&gt;different&lt;/strong&gt; zone from subnet A, IPv4 CIDR: &lt;code&gt;10.20.2.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Create subnet&lt;/strong&gt; at the bottom&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6d7yp8xoa6j2ym59vku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6d7yp8xoa6j2ym59vku.png" alt="Creating the two public subnets in different Availability Zones" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Back on the Subnets page, check the box next to one of the new subnets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Actions → Edit subnet settings&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turn on &lt;strong&gt;Auto-assign public IPv4 address&lt;/strong&gt;, then click &lt;strong&gt;Save&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat Steps 8–10 for the second subnet&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two subnets in two different zones matter here — if one zone has a&lt;br&gt;
problem, your app keeps running in the other.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 — Internet Gateway and routing
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Left sidebar → &lt;strong&gt;Internet Gateways&lt;/strong&gt; → click &lt;strong&gt;Create internet gateway&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;bluegreen-demo-igw&lt;/code&gt; → click &lt;strong&gt;Create internet gateway&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;On the gateway's page, click &lt;strong&gt;Actions → Attach to VPC&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt; → click &lt;strong&gt;Attach internet gateway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodk9hngrrl2t93y8d8vj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodk9hngrrl2t93y8d8vj.png" alt="Internet gateway attached to the VPC" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Left sidebar → &lt;strong&gt;Route Tables&lt;/strong&gt; → click &lt;strong&gt;Create route table&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name: &lt;code&gt;bluegreen-demo-public-rt&lt;/code&gt;, VPC: &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt; → click &lt;strong&gt;Create route table&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the new route table → &lt;strong&gt;Routes&lt;/strong&gt; tab → click &lt;strong&gt;Edit routes&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Add route&lt;/strong&gt; → Destination: &lt;code&gt;0.0.0.0/0&lt;/code&gt;, Target: choose&lt;br&gt;
&lt;strong&gt;Internet Gateway&lt;/strong&gt;, then select &lt;code&gt;bluegreen-demo-igw&lt;/code&gt; → click &lt;strong&gt;Save changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cri8hslzqf37buzwfw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cri8hslzqf37buzwfw0.png" alt="Route table with a 0.0.0.0/0 route to the internet gateway" width="799" height="260"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Still on the route table, click the &lt;strong&gt;Subnet associations&lt;/strong&gt; tab → click &lt;strong&gt;Edit subnet associations&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check both &lt;code&gt;bluegreen-demo-public-a&lt;/code&gt; and &lt;code&gt;bluegreen-demo-public-b&lt;/code&gt; → click &lt;strong&gt;Save associations&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fobrdfbqzqxv6b2e08d2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fobrdfbqzqxv6b2e08d2v.png" alt="Public subnets associated with the route table" width="799" height="260"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4 — Security groups
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EC2 console&lt;/strong&gt; → left sidebar &lt;strong&gt;Security Groups&lt;/strong&gt;, or follow this &lt;a href="https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#SecurityGroups:" rel="noopener noreferrer"&gt;&lt;strong&gt;link&lt;/strong&gt;&lt;/a&gt; → click &lt;strong&gt;Create security group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;bluegreen-demo-alb-sg&lt;/code&gt;, Description: anything descriptive, VPC: &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;strong&gt;Inbound rules&lt;/strong&gt;, click &lt;strong&gt;Add rule&lt;/strong&gt;: - Type: HTTP, Source: Anywhere-IPv4 (&lt;code&gt;0.0.0.0/0&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwz06rlfit37y7h6cl5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwz06rlfit37y7h6cl5o.png" alt="ALB security group inbound rule allowing HTTP" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create security group&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create security group&lt;/strong&gt; again for the second one&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name: &lt;code&gt;bluegreen-demo-svc-sg&lt;/code&gt;, same VPC&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Inbound rules&lt;/strong&gt;, click &lt;strong&gt;Add rule&lt;/strong&gt;: Type: Custom TCP, Port: &lt;code&gt;80&lt;/code&gt;, Source: click the source field, choose &lt;strong&gt;Custom&lt;/strong&gt;, then start typing &lt;code&gt;bluegreen-demo-alb-sg&lt;/code&gt; and select it from the dropdown (not an IP address)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check the Outbound rules section before you create it.&lt;/strong&gt; The&lt;br&gt;
console pre-fills a default "All traffic" outbound rule — leave it as is, or if it's been narrowed for any reason, add: Type HTTPS, Port 443, Destination Anywhere-IPv4 (&lt;code&gt;0.0.0.0/0&lt;/code&gt;). This one is easy to miss and causes a very specific, confusing failure later: the task starts, then dies with &lt;code&gt;ResourceInitializationError: unable to pull secrets or registry auth ... dial tcp ... i/o timeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0phtz6a4nb0cu1fyqzqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0phtz6a4nb0cu1fyqzqd.png" alt="Service security group inbound and outbound rules" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create security group&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This inbound rule from Step 7 is important: it means only traffic coming through your load balancer can reach the containers; nothing else can hit them directly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5 — Push an image to ECR
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;a href="https://us-east-1.console.aws.amazon.com/ecr/get-started?region=us-east-1" rel="noopener noreferrer"&gt;&lt;strong&gt;ECR console&lt;/strong&gt;&lt;/a&gt; → click &lt;strong&gt;Create repository&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;bluegreen-demo-app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Image scan settings&lt;/strong&gt;, turn on &lt;strong&gt;Scan on push&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create repository&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Open the repository → click &lt;strong&gt;View push commands&lt;/strong&gt; (top right)&lt;/li&gt;
&lt;li&gt;A dialog shows four commands with your exact account ID and region filled in — run them locally, building from the &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;index.html&lt;/code&gt; in &lt;code&gt;pipeline-config/&lt;/code&gt; in this repo:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &amp;lt;account-id&amp;gt;.dkr.ecr.&amp;lt;region&amp;gt;.amazonaws.com
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; bluegreen-demo-app pipeline-config/
docker tag bluegreen-demo-app:latest &amp;lt;account-id&amp;gt;.dkr.ecr.&amp;lt;region&amp;gt;.amazonaws.com/bluegreen-demo-app:latest
docker push &amp;lt;account-id&amp;gt;.dkr.ecr.&amp;lt;region&amp;gt;.amazonaws.com/bluegreen-demo-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you get the error &lt;code&gt;aws: [ERROR]: Your session has expired. Please reauthenticate using 'aws login'. password is empty&lt;/code&gt;,&lt;br&gt;
run &lt;code&gt;aws login&lt;/code&gt; — it opens your browser to log in, then rerun the commands above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feb9x3cwlkefk23czgegw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feb9x3cwlkefk23czgegw.png" alt="Docker login, build, tag, and push commands running in the terminal" width="800" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;docker build&lt;/code&gt; fails with &lt;code&gt;permission denied while trying to connect to the Docker API at unix:///var/run/docker.sock&lt;/code&gt;, Docker itself is fine — your user just isn't in the &lt;code&gt;docker&lt;/code&gt; group on this machine, so it can't talk to the Docker daemon without root. Fix it once, permanently, rather than prefixing every command with &lt;code&gt;sudo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then fully log out and back in (or reboot) — a new group membership&lt;br&gt;
doesn't apply to already-open terminal sessions. Confirm it worked with &lt;code&gt;docker run hello-world&lt;/code&gt;; if that runs without &lt;code&gt;sudo&lt;/code&gt;, you're set.&lt;/p&gt;

&lt;p&gt;Once the push finishes, click &lt;strong&gt;Close&lt;/strong&gt; on the dialog, then refresh the repository page — you should see one image listed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5flp4no1dd6awq25z2gr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5flp4no1dd6awq25z2gr.png" alt="ECR repository showing the pushed image" width="799" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step can't be skipped, even in the CloudFormation version — the ECS service you create later needs a real image to exist before it can start any tasks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6 — Load balancer, target groups, and listener rules
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EC2 console&lt;/strong&gt; → left sidebar &lt;strong&gt;Target Groups&lt;/strong&gt;, or follow this
&lt;a href="https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#TargetGroups:" rel="noopener noreferrer"&gt;&lt;strong&gt;link&lt;/strong&gt;&lt;/a&gt; → click &lt;strong&gt;Create target group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Target type: &lt;strong&gt;IP addresses&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt;, Protocol: HTTP, Port: 80, VPC:
&lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Health checks&lt;/strong&gt;, leave the path as &lt;code&gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt;, then click &lt;strong&gt;Create target group&lt;/strong&gt; (skip registering targets — ECS will do that automatically once the service exists)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat Steps 1–5 for a second one named &lt;code&gt;bluegreen-demo-tg-green&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9e6rijdqeqyt1ist2cps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9e6rijdqeqyt1ist2cps.png" alt="The blue and green target groups" width="799" height="135"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Left sidebar → &lt;strong&gt;Load Balancers&lt;/strong&gt;, or follow this&lt;br&gt;
&lt;a href="https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#LoadBalancers:" rel="noopener noreferrer"&gt;&lt;strong&gt;link&lt;/strong&gt;&lt;/a&gt; → click &lt;strong&gt;Create load balancer&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under &lt;strong&gt;Application Load Balancer&lt;/strong&gt;, click &lt;strong&gt;Create&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjumdj7t5k70vi6itb8zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjumdj7t5k70vi6itb8zm.png" alt="Application Load Balancer creation screen" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name: &lt;code&gt;bluegreen-demo-alb&lt;/code&gt;, Scheme: &lt;strong&gt;Internet-facing&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network mapping: VPC &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;, select both public subnets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security groups: remove the default one, add &lt;code&gt;bluegreen-demo-alb-sg&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Listeners and routing&lt;/strong&gt;, Listener 1 is already HTTP:80 — set its default action to forward to &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt; (this is just a fallback; the real routing happens through the rules you add next)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create load balancer&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once it's created, open the HTTP:80 listener → click &lt;strong&gt;Add rules&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both the production rule and the test rule live on this &lt;strong&gt;same&lt;/strong&gt; listener — the ECS "Create service" wizard only lets you choose one listener, then pick two different rules under it, so a second port isn't part of the picture here. The two rules are told apart by a header instead: normal requests fall through to the catch-all production rule, and requests carrying a specific test header get caught by the test rule first.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rule name: &lt;code&gt;test-rule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add condition: choose &lt;strong&gt;HTTP Header&lt;/strong&gt;, header name &lt;code&gt;X-Bg-Test&lt;/code&gt;, value &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Action: forward to &lt;code&gt;bluegreen-demo-tg-green&lt;/code&gt;, then click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Priority: &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Add rule&lt;/strong&gt; icon again to add the production rule&lt;/li&gt;
&lt;li&gt;Rule name: &lt;code&gt;prod-rule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add condition: &lt;strong&gt;Path&lt;/strong&gt; → value &lt;code&gt;/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Action: forward to &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt;, then click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Priority: &lt;code&gt;2&lt;/code&gt; (higher number = checked after the test rule)&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvsbtbhk9th13kvur2eh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvsbtbhk9th13kvur2eh.png" alt="ALB listener rules: test rule at priority 1, production rule at priority 2" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7 — IAM roles
&lt;/h3&gt;

&lt;p&gt;Go to the &lt;a href="https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/home" rel="noopener noreferrer"&gt;&lt;strong&gt;IAM console&lt;/strong&gt;&lt;/a&gt; → left sidebar &lt;strong&gt;Roles&lt;/strong&gt; → click &lt;strong&gt;Create role&lt;/strong&gt;,&lt;br&gt;
for each of these four:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;AWS service&lt;/strong&gt;, Use case: search and select
&lt;strong&gt;Elastic Container Service&lt;/strong&gt;, then select &lt;strong&gt;Elastic Container Service Task&lt;/strong&gt; from the use case list that appears → click &lt;strong&gt;Next&lt;/strong&gt; → search for and check &lt;code&gt;AmazonECSTaskExecutionRolePolicy&lt;/code&gt; → click &lt;strong&gt;Next&lt;/strong&gt; → name it &lt;strong&gt;&lt;code&gt;bluegreen-demo-task-exec-role&lt;/code&gt;&lt;/strong&gt;, click &lt;strong&gt;Create role&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;AWS service&lt;/strong&gt;, Use case: search and select
&lt;strong&gt;Elastic Container Service&lt;/strong&gt;, then select &lt;strong&gt;Elastic Container Service Task&lt;/strong&gt; from the use case list that appears → click &lt;strong&gt;Next&lt;/strong&gt; → don't attach any policy yet (this demo app doesn't call other AWS services) → click &lt;strong&gt;Next&lt;/strong&gt; → name it &lt;strong&gt;&lt;code&gt;bluegreen-demo-task-role&lt;/code&gt;&lt;/strong&gt;, click &lt;strong&gt;Create role&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;AWS service&lt;/strong&gt;, Use case: search for &lt;strong&gt;Elastic Container Service&lt;/strong&gt;, then choose &lt;strong&gt;"Elastic Container Service for Load Balancers"&lt;/strong&gt; → click &lt;strong&gt;Next&lt;/strong&gt; →  &lt;code&gt;AmazonECSInfrastructureRolePolicyForLoadBalancers&lt;/code&gt; should already be attached → click &lt;strong&gt;Next&lt;/strong&gt; → name it &lt;strong&gt;&lt;code&gt;bluegreen-demo-ecs-infra-role&lt;/code&gt;&lt;/strong&gt;, click &lt;strong&gt;Create role&lt;/strong&gt;. (This role is new — it's what lets ECS itself flip the listener rule between blue and green during a deployment, replacing what CodeDeploy's role used to do.)&lt;/li&gt;
&lt;li&gt;Trusted entity type: &lt;strong&gt;AWS service&lt;/strong&gt;, Use case: search for and select &lt;strong&gt;CodeBuild&lt;/strong&gt; → click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now attach the actual permissions this role needs before you ever create the CodeBuild project — this is the fix for a very common failure where the pipeline builds fine but &lt;code&gt;docker push&lt;/code&gt; fails at the Build stage, because the role CodeBuild auto-generates for you by default only covers logging and basic artifact access, not ECR. Doing it here, once, means you never have to come back and patch it later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Create inline policy&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Switch to the &lt;strong&gt;JSON&lt;/strong&gt; tab, delete the placeholder content, and paste this:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketAcl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketLocation"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecr:GetAuthorizationToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:BatchCheckLayerAvailability"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:GetDownloadUrlForLayer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:BatchGetImage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:PutImage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:InitiateLayerUpload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:UploadLayerPart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:CompleteLayerUpload"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ecr:*:*:repository/bluegreen-demo-app"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Name it &lt;code&gt;bluegreen-demo-codebuild-role&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Name the policy &lt;code&gt;bluegreen-demo-codebuild-inline&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create role&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The S3 permissions use &lt;code&gt;Resource: "*"&lt;/code&gt; here rather than a specific bucket ARN, because the pipeline's artifact bucket doesn't exist yet — it gets created in Step 10. That's loose for a real production setup (worth tightening to the actual bucket ARN once you know it), but fine for getting this working the first time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs2zjhpbrub5i33ipboxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs2zjhpbrub5i33ipboxs.png" alt="IAM roles created for the project" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 8 — ECS cluster, task definition, and service
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ECS console&lt;/strong&gt;, or follow this
&lt;a href="https://us-east-1.console.aws.amazon.com/ecs/v2/getStarted?region=us-east-1" rel="noopener noreferrer"&gt;&lt;strong&gt;link&lt;/strong&gt;&lt;/a&gt; → left sidebar &lt;strong&gt;Clusters&lt;/strong&gt; → click &lt;strong&gt;Create cluster&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;bluegreen-demo-cluster&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Infrastructure: check &lt;strong&gt;AWS Fargate (serverless)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Left sidebar → &lt;strong&gt;Task definitions&lt;/strong&gt; → click &lt;strong&gt;Create new task definition&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Task definition family: &lt;code&gt;bluegreen-demo-task&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Launch type: &lt;strong&gt;AWS Fargate&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;CPU: &lt;code&gt;.25 vCPU&lt;/code&gt;, Memory: &lt;code&gt;0.5 GB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Task role: &lt;code&gt;bluegreen-demo-task-role&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Task execution role: &lt;code&gt;bluegreen-demo-task-exec-role&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Container 1&lt;/strong&gt;: name &lt;code&gt;bluegreen-demo-container&lt;/code&gt;, Image URI: paste the ECR image URI from Step 5, Container port: &lt;code&gt;80&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Logging&lt;/strong&gt;, turn on &lt;strong&gt;Use log collection&lt;/strong&gt;, log group name &lt;code&gt;/ecs/bluegreen-demo&lt;/code&gt;, leave everything else as it is&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faa7uog40bhflut4u1r95.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faa7uog40bhflut4u1r95.png" alt="ECS task definition configuration" width="799" height="332"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the cluster &lt;code&gt;bluegreen-demo-cluster&lt;/code&gt; → click &lt;strong&gt;Create service&lt;/strong&gt; (or from the task definition page, use the &lt;strong&gt;Deploy&lt;/strong&gt; menu → &lt;strong&gt;Create service&lt;/strong&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Task definition family&lt;/strong&gt;, choose the one we created earlier: &lt;code&gt;bluegreen-demo-task&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Task definition revision: &lt;strong&gt;latest&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service name: &lt;code&gt;bluegreen-demo-service&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compute options: &lt;strong&gt;Launch type&lt;/strong&gt;, &lt;code&gt;FARGATE&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Desired tasks: &lt;code&gt;2&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Deployment options&lt;/strong&gt;, choose &lt;strong&gt;Deployment strategy: Blue/green&lt;/strong&gt; — this is the setting that replaces what used to require a separate CodeDeploy application, don't miss it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Deployment bake time&lt;/strong&gt;, type &lt;code&gt;3&lt;/code&gt;. AWS defaults this to 15 minutes, which makes iterating on a test setup painfully slow — see the note right after Step 11 for exactly what this setting does and doesn't control before you assume a short bake time is unsafe&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Deployment lifecycle hooks&lt;/strong&gt;, click &lt;strong&gt;Add&lt;/strong&gt;. This is the part that actually gates production cutover — unlike everything above it, which just configures &lt;em&gt;how&lt;/em&gt; the shift happens, not &lt;em&gt;whether&lt;/em&gt; it's allowed to happen without anyone checking first&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose hook type &lt;strong&gt;Pause&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lifecycle stages: select &lt;strong&gt;PRE_PRODUCTION_TRAFFIC_SHIFT&lt;/strong&gt; — this pauses right after green has already received test traffic, but before it gets anything real&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set a timeout — 60 minutes is reasonable for testing. Leave the timeout action as &lt;strong&gt;Roll back&lt;/strong&gt; (the default): if you forget to approve it, an unattended deployment should fail safe and revert, not quietly go live on its own&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Networking&lt;/strong&gt;: VPC &lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;, both public subnets, security group &lt;code&gt;bluegreen-demo-svc-sg&lt;/code&gt;, turn on &lt;strong&gt;Public IP&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Load balancing&lt;/strong&gt;: choose &lt;strong&gt;Application Load Balancer&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Load balancer: &lt;code&gt;bluegreen-demo-alb&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose the VPC you created (&lt;code&gt;bluegreen-demo-vpc&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under the role, choose &lt;code&gt;bluegreen-demo-ecs-infra-role&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Container to load balance: &lt;code&gt;bluegreen-demo-container 80:80&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Load Balancer Type&lt;/strong&gt;, choose &lt;strong&gt;Application Load Balancer&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Application Load Balancer&lt;/strong&gt;, choose &lt;strong&gt;Use an existing load balancer&lt;/strong&gt;, then choose &lt;code&gt;bluegreen-demo-alb&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Listener&lt;/strong&gt;, click &lt;strong&gt;Use an existing listener&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose &lt;strong&gt;HTTP:80&lt;/strong&gt; — under &lt;strong&gt;Production listener rule&lt;/strong&gt; choose    &lt;strong&gt;Priority: 2&lt;/strong&gt;, and under &lt;strong&gt;Test listener rule&lt;/strong&gt; choose    &lt;strong&gt;Priority: 1&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Target group: &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Green target group: &lt;code&gt;bluegreen-demo-tg-green&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp5vk5kshrev6phosija.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp5vk5kshrev6phosija.png" alt="ECS service creation screen" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Creating the service kicks off its first deployment, and since manual approval is on, this first deployment pauses too, just like every one after it will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the ECS service → deployment timeline to watch it progress through each phase in real time&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The deployment should stop at &lt;strong&gt;Test traffic shift&lt;/strong&gt;, showing status &lt;strong&gt;Awaiting action&lt;/strong&gt; — this is the pause hook you just set up&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy8qi759wfak3ijre0s0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy8qi759wfak3ijre0s0z.png" alt="First deployment paused at Test traffic shift, showing Awaiting action" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Take Action → Continue&lt;/strong&gt; to let it proceed to &lt;strong&gt;Production traffic shift&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs49izl7rib8wg1671bn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs49izl7rib8wg1671bn0.png" alt="Take Action menu with the Continue option" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 8a — Confirm the blue environment actually works
&lt;/h3&gt;

&lt;p&gt;Worth stopping here before building the pipeline. Everything from Steps 1–8 is the actual infrastructure — the VPC, security groups, load balancer, target groups, listener rules, and the service itself. If something's wrong with any of it, you want to find that out now, not after you've also added CodeBuild and CodePipeline on top and have to guess which layer the problem is in.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the ECS service → wait for both tasks under &lt;strong&gt;Tasks&lt;/strong&gt; to show status &lt;strong&gt;Running&lt;/strong&gt; and health status &lt;strong&gt;Healthy&lt;/strong&gt; (give it a minute or two after creation)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbyj6t558cemvsiv87f8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbyj6t558cemvsiv87f8d.png" alt="ECS service with both tasks running and healthy" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the &lt;strong&gt;EC2 console → Load Balancers → &lt;code&gt;bluegreen-demo-alb&lt;/code&gt;&lt;/strong&gt; and copy the &lt;strong&gt;DNS name&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open that DNS name in a browser, or run:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;alb-dns-name&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvz48fbxu5we94gzefjxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvz48fbxu5we94gzefjxt.png" alt="curl response from the load balancer showing Version v1" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see the placeholder page's content (&lt;code&gt;Version: v1&lt;/code&gt;). If you get a timeout, a 503, or nothing at all, stop here and check, in this order: the tasks are actually healthy in the target group (EC2 console → Target Groups → &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt; → &lt;strong&gt;Targets&lt;/strong&gt; tab — both should show &lt;strong&gt;healthy&lt;/strong&gt;, not &lt;strong&gt;unhealthy&lt;/strong&gt; or &lt;strong&gt;draining&lt;/strong&gt;), the service security group's outbound rule allows HTTPS (Step 4 — needed to even pull the image and start), and the production listener rule is actually pointing at &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt; (Step 6).&lt;/p&gt;

&lt;p&gt;Once this works, you know the infrastructure itself is solid, and any problems from here on are isolated to the pipeline you're about to build.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 9 — CodeBuild project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://us-east-1.console.aws.amazon.com/codesuite/codebuild/projects?region=us-east-1" rel="noopener noreferrer"&gt;&lt;strong&gt;CodeBuild console&lt;/strong&gt;&lt;/a&gt; → click &lt;strong&gt;Create build project&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Project name: &lt;code&gt;bluegreen-demo-build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Source provider: &lt;strong&gt;GitHub&lt;/strong&gt;. Under &lt;strong&gt;Credential&lt;/strong&gt;, click &lt;strong&gt;Use override credentials for this project only&lt;/strong&gt;. Under &lt;strong&gt;Connection&lt;/strong&gt;, click &lt;strong&gt;Create a new OAuth app token connection&lt;/strong&gt;, follow the prompt to log in to your GitHub account, and save the secret name as anything you like. Choose the connection you just created, then paste your repo URL into the GitHub Repository field — for example,  &lt;code&gt;https://github.com/Gbengard/blue-green-ecs-deployment&lt;/code&gt; — and your branch name, for example, &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Environment image: &lt;strong&gt;Managed image&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Operating system: &lt;strong&gt;Amazon Linux&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Runtime: &lt;strong&gt;Standard&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Image: use the latest available standard image&lt;/li&gt;
&lt;li&gt;Service role: &lt;strong&gt;Existing service role&lt;/strong&gt; → select &lt;code&gt;bluegreen-demo-codebuild-role&lt;/code&gt; (the one you built with full permissions back in Step 7 — no need to create a new one here, and nothing to patch afterward)&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Enable this flag if you want to build Docker images or want your builds to get elevated privileges&lt;/strong&gt; — required, since building a Docker image inside CodeBuild needs this&lt;/li&gt;
&lt;li&gt;Expand &lt;strong&gt;Additional configuration&lt;/strong&gt;, scroll to &lt;strong&gt;Environment variables&lt;/strong&gt;, and add these four — the buildspec reads them and the build fails (or silently builds a malformed ECR URL) without them:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- `AWS_REGION` = your region, e.g. `us-east-1`. CodeBuild is supposed to provide this automatically as a built-in variable, but in practice it didn't always resolve reliably inside the buildspec's shell commands — setting it explicitly here removes the ambiguity and is cheap insurance against a hard-to-diagnose malformed-URL error.
- `AWS_ACCOUNT_ID` = your 12-digit account ID (run `aws sts get-caller-identity --query Account --output text` if you don't have it memorized)
- `ECR_REPO_NAME` = `bluegreen-demo-app`
- `CONTAINER_NAME` = `bluegreen-demo-container`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Buildspec: choose &lt;strong&gt;Use a buildspec file&lt;/strong&gt;, path &lt;code&gt;pipeline-config/buildspec.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create build project&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbxxn0ccq7nm99paqct7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbxxn0ccq7nm99paqct7.png" alt="CodeBuild project configuration" width="799" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 10 — CodePipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://us-east-1.console.aws.amazon.com/codesuite/codepipeline/home?region=us-east-1" rel="noopener noreferrer"&gt;&lt;strong&gt;CodePipeline console&lt;/strong&gt;&lt;/a&gt; → click &lt;strong&gt;Create pipeline&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Build custom pipeline&lt;/strong&gt; → click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Pipeline name: &lt;code&gt;bluegreen-demo-pipeline&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Execution mode: leave as &lt;strong&gt;Queued&lt;/strong&gt; → click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Source provider: &lt;strong&gt;GitHub (via OAuth App)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Connect to GitHub with OAuth&lt;/strong&gt; — this opens the authorization screen; a person has to click through this manually&lt;/li&gt;
&lt;li&gt;Once connected, choose your repository and branch → click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Build provider: &lt;strong&gt;AWS CodeBuild&lt;/strong&gt;, project: &lt;code&gt;bluegreen-demo-build&lt;/code&gt; → click &lt;strong&gt;Next&lt;/strong&gt; twice&lt;/li&gt;
&lt;li&gt;Deploy provider: &lt;strong&gt;Amazon ECS&lt;/strong&gt; (not "Amazon ECS (Blue/Green)" — that older option is the CodeDeploy-powered one; plain &lt;strong&gt;Amazon ECS&lt;/strong&gt; is what you want here, since the service itself already handles the blue/green shift)&lt;/li&gt;
&lt;li&gt;Cluster name: &lt;code&gt;bluegreen-demo-cluster&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Service name: &lt;code&gt;bluegreen-demo-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Image definitions file: &lt;code&gt;imagedefinitions.json&lt;/code&gt; → click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Review everything → click &lt;strong&gt;Create pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpaa8ytxhvd0qr2hrjsv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpaa8ytxhvd0qr2hrjsv1.png" alt="CodePipeline creation screen" width="799" height="298"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The pipeline triggers automatically&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the ECS service → &lt;strong&gt;Deployments&lt;/strong&gt;, and when it asks you to &lt;strong&gt;Take Action&lt;/strong&gt;, click it, then click &lt;strong&gt;Continue&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait until the deployment and the pipeline both show as successful&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3hin4ta00431yviwq4yw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3hin4ta00431yviwq4yw.png" alt="Pipeline execution completed successfully" width="799" height="298"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 11 — Test it
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Push a small change to your GitHub repo (edit the text in
&lt;code&gt;pipeline-config/index.html&lt;/code&gt; — change &lt;code&gt;Version: v1&lt;/code&gt; to &lt;code&gt;Version: v2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Watch the pipeline run in the CodePipeline console&lt;/li&gt;
&lt;li&gt;Open the ECS service → deployment timeline to watch it progress through each phase in real time&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While it's in &lt;strong&gt;Scaling up green tasks&lt;/strong&gt; or &lt;strong&gt;Test traffic shift&lt;/strong&gt;, hit the load balancer URL with the test header set — a browser can't add custom headers easily, so use curl: &lt;code&gt;curl -H "X-Bg-Test: true" http://&amp;lt;alb-dns-name&amp;gt;/&lt;/code&gt; — you should see the new version there, before it's live&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvvumngi0twjcg0f4z25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvvumngi0twjcg0f4z25.png" alt="curl with the X-Bg-Test header returning the new version before cutover" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The deployment should now stop at &lt;strong&gt;Test traffic shift&lt;/strong&gt;, showing status &lt;strong&gt;Awaiting action&lt;/strong&gt; — this is the pause hook from Step 8 working. At this point, real traffic is still on the old version; confirm that with a plain &lt;code&gt;curl http://&amp;lt;alb-dns-name&amp;gt;/&lt;/code&gt; (no header) — it should still show the old version&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fglnbhp4d053dk1kv61vm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fglnbhp4d053dk1kv61vm.png" alt="Deployment paused at Test traffic shift, awaiting manual approval" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you're satisfied the new version looks right, click &lt;strong&gt;Take Action → Continue&lt;/strong&gt; to let it proceed to &lt;strong&gt;Production traffic shift&lt;/strong&gt;. (Or click &lt;strong&gt;Roll back&lt;/strong&gt; instead, to see what happens when you reject a deployment outright — the new tasks never get production traffic at all)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pnw44xudzlmibg5lgeh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pnw44xudzlmibg5lgeh.png" alt="Clicking Continue to approve the deployment" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once it continues, refresh the load balancer's DNS name normally (no header) — it should now show the new version&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F637sj29bh0qfh01p7vjb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F637sj29bh0qfh01p7vjb.png" alt="curl without the header returning the new version after cutover" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For a stronger test, open a terminal and run a loop hitting the URL once a second from before you click Continue through to deployment complete — watch that it never returns an error the whole way through the swap&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  What bake time actually controls (it's not what it sounds like)
&lt;/h4&gt;

&lt;p&gt;It's worth understanding exactly what bake time does and doesn't control, because the name suggests it should delay something, and it does, just not the thing you'd guess — and it's easy to conflate it with the manual approval gate from Step 8, which is a genuinely different mechanism doing a genuinely different job.&lt;/p&gt;

&lt;p&gt;The deployment actually happens in this order:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9xt7j2ctanlez6wheh25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9xt7j2ctanlez6wheh25.png" alt="Order of phases in a blue-green deployment" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;New (green) tasks launch and register to the alternate target group&lt;/li&gt;
&lt;li&gt;As soon as they pass health checks, the &lt;strong&gt;test rule&lt;/strong&gt; can reach them — immediately, with no delay. This is intentional: the entire point of the test rule is to let you validate the new version &lt;em&gt;before&lt;/em&gt; real traffic hits it, so of course it has to be reachable right away&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is where the manual approval hook (Step 8) actually stops
things&lt;/strong&gt; — the deployment pauses here, at &lt;code&gt;PRE_PRODUCTION_TRAFFIC_SHIFT&lt;/code&gt;, and waits for you to click Continue. This is the one genuine gate in the whole sequence: nothing proceeds past this point without it&lt;/li&gt;
&lt;li&gt;Once approved, the &lt;strong&gt;production rule&lt;/strong&gt; shifts over to green&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only now does bake time begin&lt;/strong&gt; — and all it does is keep the old (blue) tasks running for that long, as a fast-rollback safety net, before finally terminating them&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So bake time and the approval hook are doing two different jobs, on two different sides of the same moment: the approval hook gates &lt;em&gt;whether and when&lt;/em&gt; cutover happens at all; bake time only controls how long the &lt;em&gt;old&lt;/em&gt; version sticks around &lt;em&gt;after&lt;/em&gt; cutover, in case something needs to roll back quickly. A 3-minute bake time and a 15-minute one behave identically for how fast your change goes live once approved; the difference only shows up if something goes wrong shortly after cutover.&lt;/p&gt;

&lt;p&gt;If you skipped the pause hook in Step 8 (or set the &lt;code&gt;EnableManualApproval&lt;/code&gt; parameter to &lt;code&gt;false&lt;/code&gt; in the CloudFormation&lt;br&gt;
version), step 3 above wouldn't exist — the production shift would&lt;br&gt;
happen automatically the moment green is healthy, with nothing gating it at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is traffic ever split between blue and green while this is happening? No — not with this project's setup.&lt;/strong&gt; ECS actually offers three different deployment strategies: &lt;code&gt;BLUE_GREEN&lt;/code&gt;, &lt;code&gt;LINEAR&lt;/code&gt;, and &lt;code&gt;CANARY&lt;/code&gt;. Only &lt;code&gt;LINEAR&lt;/code&gt; and &lt;code&gt;CANARY&lt;/code&gt; shift traffic by percentage (for example, &lt;code&gt;CANARY&lt;/code&gt; sends 10% of real traffic to green, waits, then shifts the remaining 90% all at once; &lt;code&gt;LINEAR&lt;/code&gt; moves it in equal steps over time). Plain &lt;code&gt;BLUE_GREEN&lt;/code&gt; — what this project's &lt;code&gt;DeploymentConfiguration.Strategy&lt;/code&gt; is actually set to — does none of that. It's a single atomic switch: one moment production is 100% on blue; the instant green passes its health&lt;br&gt;
checks, production becomes 100% on green. There's no window where real users are being split between old and new. If you specifically want gradual, percentage-based exposure to real traffic — not just the private test-header path — that's a different strategy (&lt;code&gt;LINEAR&lt;/code&gt; or &lt;code&gt;CANARY&lt;/code&gt;), not something this &lt;code&gt;BLUE_GREEN&lt;/code&gt; setup does.&lt;/p&gt;

&lt;p&gt;This project gates the cutover — that's exactly what the&lt;br&gt;
manual approval hook from Step 8 is for, and it's covered in detail in the "What bake time actually controls" section above. What's still missing, if you want a bad deployment to be rejected &lt;em&gt;automatically&lt;/em&gt; rather than relying on a human to notice and click Reject, is a CloudWatch alarm attached to the deployment configuration — ECS would watch it and auto-rollback if it fires. That's not set up here; it's listed under "What I'd change for a production version" at the end of this guide.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 12 — Roll back on purpose
&lt;/h3&gt;

&lt;p&gt;Push a change that breaks the health check (for example, edit the task definition to point at a bad image tag) and watch the ECS service's deployment fail and keep the blue target group serving traffic. Doing this once, on purpose, is the only way to actually see a rollback happen instead of just reading about one.&lt;/p&gt;


&lt;h2&gt;
  
  
  Manual Teardown (Console)
&lt;/h2&gt;

&lt;p&gt;Delete in this order. AWS will block you if you try to delete something still in use by another resource — for example, it won't let you delete a VPC that still has subnets in it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CodePipeline&lt;/strong&gt; — open the pipeline → click &lt;strong&gt;Actions → Delete   pipeline&lt;/strong&gt; → type the pipeline name to confirm → click &lt;strong&gt;Delete&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodePipeline's S3 artifact bucket&lt;/strong&gt; — this one's easy to forget, since the console creates it for you automatically and doesn't call attention to it again. Go to the &lt;a href="https://us-east-1.console.aws.amazon.com/s3/home?region=us-east-1#" rel="noopener noreferrer"&gt;&lt;strong&gt;S3 console&lt;/strong&gt;&lt;/a&gt; and look for a bucket named something like &lt;code&gt;codepipeline-&amp;lt;region&amp;gt;-&amp;lt;random-id&amp;gt;&lt;/code&gt; (or whatever you named it, if you
specified a custom one when creating the pipeline). Open it, select all objects, click &lt;strong&gt;Delete&lt;/strong&gt;, type &lt;code&gt;permanently delete&lt;/code&gt; to confirm, then go back and delete the bucket itself the same way. It has to be empty before S3 will let you delete it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeBuild&lt;/strong&gt; — open the project → &lt;strong&gt;Delete build project&lt;/strong&gt; → confirm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Manager&lt;/strong&gt; — go to the &lt;a href="https://us-east-1.console.aws.amazon.com/secretsmanager/listsecrets?region=us-east-1" rel="noopener noreferrer"&gt;&lt;strong&gt;Secrets Manager console&lt;/strong&gt;&lt;/a&gt;, click the secret CodeBuild created for your GitHub OAuth connection → &lt;strong&gt;Actions → Delete&lt;/strong&gt; → set the waiting period to 7 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS service&lt;/strong&gt; — open the service → click &lt;strong&gt;Delete service&lt;/strong&gt; → click &lt;strong&gt;Force delete&lt;/strong&gt; → type &lt;code&gt;delete&lt;/code&gt; to confirm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS cluster&lt;/strong&gt; — once the service is gone, open the cluster → click &lt;strong&gt;Delete cluster&lt;/strong&gt; → type the cluster name to confirm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task definitions&lt;/strong&gt; — open &lt;strong&gt;Task definitions → bluegreen-demo-task&lt;/strong&gt;, select all revisions, click &lt;strong&gt;Deregister&lt;/strong&gt; (this marks them inactive — they'll still show up in the list unless you also permanently delete them). To fully remove them: change the filter to &lt;strong&gt;Inactive&lt;/strong&gt;, select the same revisions, click &lt;strong&gt;Actions → Delete&lt;/strong&gt; — this option only appears once a revision is already deregistered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancer&lt;/strong&gt; — open &lt;code&gt;bluegreen-demo-alb&lt;/code&gt; → &lt;strong&gt;Actions → Delete load balancer&lt;/strong&gt; → confirm (delete this first, before the target groups, since it's using them)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target groups&lt;/strong&gt; — check &lt;code&gt;bluegreen-demo-tg-blue&lt;/code&gt; and   &lt;code&gt;bluegreen-demo-tg-green&lt;/code&gt;, then &lt;strong&gt;Actions → Delete&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR repository&lt;/strong&gt; — open &lt;code&gt;bluegreen-demo-app&lt;/code&gt; → &lt;strong&gt;Delete&lt;/strong&gt; → type the repository name to confirm (this force-deletes it along with any images still inside)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch log group&lt;/strong&gt; — go to &lt;strong&gt;CloudWatch console → Log    Management → Log groups&lt;/strong&gt;, select &lt;code&gt;/ecs/bluegreen-demo&lt;/code&gt;, click    &lt;strong&gt;Actions → Delete log group&lt;/strong&gt; → confirm. Nothing else deletes this one for you — it sits there quietly running up a small storage cost until you remove it yourself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM roles&lt;/strong&gt; — delete the four roles created in Step 7, plus the service role the CodePipeline console created for the pipeline in Step 10 (look for a role whose name starts with  &lt;code&gt;AWSCodePipelineServiceRole&lt;/code&gt;): select each one → &lt;strong&gt;Delete&lt;/strong&gt; → type the role name to confirm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security groups&lt;/strong&gt; — delete &lt;code&gt;bluegreen-demo-svc-sg&lt;/code&gt; first, then &lt;code&gt;bluegreen-demo-alb-sg&lt;/code&gt; (the service one references the ALB one, so it has to go first)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC networking&lt;/strong&gt; — delete both subnets, then the route table, then detach and delete the internet gateway, then finally delete the VPC itself, using &lt;strong&gt;Actions → Delete&lt;/strong&gt; on each one&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go back through the VPC, EC2, ECS, ECR, S3, and CloudWatch consoles&lt;br&gt;
afterward and check nothing was left behind — a resource with a&lt;br&gt;
dependency you missed will sometimes fail quietly instead of showing a clear error.&lt;/p&gt;


&lt;h2&gt;
  
  
  Automated Approach (CloudFormation)
&lt;/h2&gt;

&lt;p&gt;Same architecture as above — including ECS's native blue/green&lt;br&gt;
deployment strategy, not CodeDeploy — but split across &lt;strong&gt;two&lt;/strong&gt; templates instead of one, specifically so the two manual steps (authorizing the GitHub connection, pushing the first image) can happen &lt;em&gt;before&lt;/em&gt; anything that depends on them starts running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cloudformation/00-prerequisites-stack.yaml&lt;/code&gt;&lt;/strong&gt; — just the ECR repo and the GitHub connection. Deploy this first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cloudformation/blue-green-ecs-stack.yaml&lt;/code&gt;&lt;/strong&gt; — everything else (VPC, ALB, ECS service, pipeline). Deploy this second, pointing it at the prerequisites stack's outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you went through the console steps first, most of this will look&lt;br&gt;
familiar — it's the same resources, just as code.&lt;/p&gt;

&lt;p&gt;Reminder from the Prerequisites section: you still need the&lt;br&gt;
&lt;code&gt;pipeline-config&lt;/code&gt; files pushed to your own GitHub repo before the&lt;br&gt;
pipeline will fully work, since it pulls its source from there.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deploy it — two stacks, in order
&lt;/h3&gt;

&lt;p&gt;The ECS service and the pipeline in the main stack both start trying to work the moment the stack finishes creating — the service starts placing tasks, the pipeline's Source stage starts polling GitHub. Neither of those can actually succeed until an image exists in ECR and the GitHub connection is authorized. So rather than create everything at once and watch it fail predictably, deploy a small prerequisites stack first, do the two manual steps against it, &lt;em&gt;then&lt;/em&gt; deploy the main stack — by which point everything it needs already exists.&lt;/p&gt;
&lt;h4&gt;
  
  
  Stage 1 — prerequisites stack (ECR repo + GitHub connection)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation create-stack &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-body&lt;/span&gt; file://cloudformation/00-prerequisites-stack.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameters&lt;/span&gt; &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ProjectName,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;bluegreen-demo

aws cloudformation &lt;span class="nb"&gt;wait &lt;/span&gt;stack-create-complete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Grab its outputs — you'll need these for both the manual steps below and Stage 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Stage 2 — the two manual steps (now, before the main stack exists)
&lt;/h4&gt;

&lt;p&gt;These aren't gaps in either template — AWS deliberately requires a human for both, for security reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Authorize the GitHub connection.&lt;/strong&gt; Go to the &lt;strong&gt;Developer Tools&lt;/strong&gt; console → &lt;strong&gt;Settings → Connections&lt;/strong&gt; in the left sidebar → find &lt;code&gt;bluegreen-demo-github-connection&lt;/code&gt; (status &lt;strong&gt;Pending&lt;/strong&gt;) → click it → click &lt;strong&gt;Update pending connection&lt;/strong&gt; → follow the GitHub authorization prompt → click &lt;strong&gt;Connect&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Push the first image to ECR.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-caller-identity &lt;span class="nt"&gt;--query&lt;/span&gt; Account &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws configure get region&lt;span class="si"&gt;)&lt;/span&gt;

aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$REGION&lt;/span&gt; | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt;.dkr.ecr.&lt;span class="nv"&gt;$REGION&lt;/span&gt;.amazonaws.com
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; bluegreen-demo-app pipeline-config/
docker tag bluegreen-demo-app:latest &lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt;.dkr.ecr.&lt;span class="nv"&gt;$REGION&lt;/span&gt;.amazonaws.com/bluegreen-demo-app:latest
docker push &lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt;.dkr.ecr.&lt;span class="nv"&gt;$REGION&lt;/span&gt;.amazonaws.com/bluegreen-demo-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of these can happen in either order relative to each other — just both before Stage 3.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stage 3 — the main stack
&lt;/h4&gt;

&lt;p&gt;Now take the &lt;code&gt;GitHubConnectionArn&lt;/code&gt; and &lt;code&gt;EcrRepositoryName&lt;/code&gt; values from the prerequisites stack's outputs and pass them in here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CONN_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Stacks[0].Outputs[?OutputKey=='GitHubConnectionArn'].OutputValue"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ECR_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Stacks[0].Outputs[?OutputKey=='EcrRepositoryName'].OutputValue"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

aws cloudformation create-stack &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-body&lt;/span&gt; file://cloudformation/blue-green-ecs-stack.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameters&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ProjectName,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;bluegreen-demo &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;EcrRepositoryName,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ECR_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;GitHubConnectionArn,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$CONN_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;GitHubRepoOwner,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-github-username&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;GitHubRepoName,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-repo-name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;GitHubBranch,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BakeTimeMinutes,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;EnableManualApproval,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ApprovalTimeoutMinutes,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;60 &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ApprovalTimeoutAction,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;ROLLBACK

aws cloudformation &lt;span class="nb"&gt;wait &lt;/span&gt;stack-create-complete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BakeTimeMinutes&lt;/code&gt; defaults to 3 — AWS's own default is 15, a long wait while you're iterating. &lt;code&gt;EnableManualApproval&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;, adding a &lt;code&gt;PAUSE&lt;/code&gt; hook at &lt;code&gt;PRE_PRODUCTION_TRAFFIC_SHIFT&lt;/code&gt; — no Lambda or extra IAM role needed; it's a plain ECS feature. Set it to &lt;code&gt;false&lt;/code&gt; if you'd rather the deployment shift to production automatically the moment green is healthy, with nothing waiting on anyone. &lt;code&gt;ApprovalTimeoutAction&lt;/code&gt; defaults to &lt;code&gt;ROLLBACK&lt;/code&gt;, meaning a deployment nobody approves in time reverts safely rather than quietly going live. See the note after Step 11 in the console section for exactly how this hook and bake time relate to each&lt;br&gt;
other — they gate two different moments, not the same one.&lt;/p&gt;

&lt;p&gt;By the time this stack finishes creating, the image already exists and the connection is already authorized — the ECS service should start tasks successfully on the first try, and the pipeline's Source stage should work the first time you push a commit, with none of the expected-but-confusing-looking failures you'd get from creating everything in one shot.&lt;/p&gt;
&lt;h3&gt;
  
  
  Check what you got
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This prints the ALB URL, pipeline name, and ECS cluster/service names, so you don't have to go hunting through the console. There's no separate test URL — the same URL serves both blue and green, told apart by a header (see below).&lt;/p&gt;
&lt;h3&gt;
  
  
  Test it
&lt;/h3&gt;

&lt;p&gt;Push a commit to your GitHub repo, watch the pipeline run, then confirm zero downtime during the swap. While the deployment is running, you can also check the new (green) version privately before it goes live, using the &lt;code&gt;X-Bg-Test: true&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ALB_DNS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Stacks[0].Outputs[?OutputKey=='LoadBalancerUrl'].OutputValue"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Check the new version privately, before it's live:&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Bg-Test: true"&lt;/span&gt; &lt;span class="nv"&gt;$ALB_DNS&lt;/span&gt;/

&lt;span class="c"&gt;# Confirm zero downtime for real users during the swap:&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code} "&lt;/span&gt; &lt;span class="nv"&gt;$ALB_DNS&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;200&lt;/code&gt; the whole way through the deploy, with nothing else mixed in. That's the actual proof this works, not just a green checkmark in the console.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automated Teardown (CloudFormation)
&lt;/h2&gt;

&lt;p&gt;Two stacks were created, so two need deleting — &lt;strong&gt;main stack first, then prerequisites&lt;/strong&gt;. Deleting them in the other order will leave the main stack's pipeline referencing a GitHub connection that no longer exists, and its deployments referencing an ECR repo that no longer exists.&lt;/p&gt;

&lt;p&gt;Unlike the manual teardown, you don't need separate steps for the&lt;br&gt;
CloudWatch log group or the task definition — both are resources&lt;br&gt;
CloudFormation created as part of the main stack (&lt;code&gt;EcsLogGroup&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;TaskDefinition&lt;/code&gt;), so &lt;code&gt;delete-stack&lt;/code&gt; removes them along with everything else. The task definition gets deregistered, not permanently deleted — if you want it fully gone rather than just inactive, that's an optional extra step at the end of this section.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1 — Empty the S3 artifact bucket
&lt;/h3&gt;

&lt;p&gt;CloudFormation refuses to delete a non-empty bucket. Versioning was&lt;br&gt;
turned on for this one, so a normal delete leaves old versions behind — this clears those too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://bluegreen-demo-artifacts-&lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;
aws s3api delete-objects &lt;span class="nt"&gt;--bucket&lt;/span&gt; bluegreen-demo-artifacts-&lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws s3api list-object-versions &lt;span class="nt"&gt;--bucket&lt;/span&gt; bluegreen-demo-artifacts-&lt;span class="nv"&gt;$ACCOUNT_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'{Objects: Versions[].{Key:Key,VersionId:VersionId}}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 — Delete the main stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo &lt;span class="nt"&gt;--deletion-mode&lt;/span&gt; FORCE_DELETE_STACK
aws cloudformation &lt;span class="nb"&gt;wait &lt;/span&gt;stack-delete-complete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in the console: open the stack → click &lt;strong&gt;Delete&lt;/strong&gt; → confirm in the dialog.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Empty the ECR repository
&lt;/h3&gt;

&lt;p&gt;This lives in the prerequisites stack now, so it's handled separately, after the main stack is gone (nothing in the main stack needs it anymore at this point).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr batch-delete-image &lt;span class="nt"&gt;--repository-name&lt;/span&gt; bluegreen-demo-app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-ids&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ecr list-images &lt;span class="nt"&gt;--repository-name&lt;/span&gt; bluegreen-demo-app &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'imageIds'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Delete the prerequisites stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs
aws cloudformation &lt;span class="nb"&gt;wait &lt;/span&gt;stack-delete-complete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes the ECR repo and the GitHub connection together — no need to delete the connection separately; CloudFormation still owns and tracks it even though you authorized it by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 — Confirm both are gone
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo
aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; bluegreen-demo-prereqs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both should return a "does not exist" error once deletion finishes. If either is stuck in &lt;code&gt;DELETE_FAILED&lt;/code&gt;, check that stack's &lt;strong&gt;Events&lt;/strong&gt; tab to see which resource blocked it — for the main stack it's almost always the S3 bucket not being fully empty; for the prerequisites stack, almost always the ECR repo still having images in it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost note
&lt;/h2&gt;

&lt;p&gt;This stack costs money the whole time it's running — mainly the load balancer and the Fargate tasks. Build it, test it, screenshot what you need, then tear it down rather than leaving it up. Set a billing alarm before you start, just in case something doesn't get cleaned up the way you expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change for a production version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Private subnets with a NAT Gateway, instead of public subnets with public IPs on the tasks&lt;/li&gt;
&lt;li&gt;Tighter IAM policies — the CodeBuild and CodePipeline roles here are broader than they'd need to be in a real setup&lt;/li&gt;
&lt;li&gt;HTTPS on the load balancer with a real certificate, not plain HTTP&lt;/li&gt;
&lt;li&gt;A CloudWatch alarm attached to the deployment configuration, so a spike in errors during bake time triggers an automatic rollback — right now the manual approval hook gates the &lt;em&gt;initial&lt;/em&gt; cutover, but once bake time starts, nothing but target group health checks is watching for problems&lt;/li&gt;
&lt;li&gt;A Lambda-type lifecycle hook running an actual automated check (hit a &lt;code&gt;/health&lt;/code&gt; endpoint, check a key metric) at &lt;code&gt;PRE_PRODUCTION_TRAFFIC_SHIFT&lt;/code&gt; instead of — or alongside — a human manually deciding. The manual approval hook built here is a real gate, but it's only as good as whoever's watching for the Awaiting Action status; a real production setup likely wants both an automated check and a human able to override it&lt;/li&gt;
&lt;li&gt;A longer bake time for anything handling real traffic, so there's more time to notice a problem before the old version terminates&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Troubleshooting: real errors people hit building this
&lt;/h2&gt;

&lt;p&gt;These are actual errors hit while building this project, kept here with their real fixes rather than buried in a changelog somewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CodeBuild project's Source shows "GitHub" instead of "CodePipeline" (or vice versa, and you're not sure which is right)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When this project is being used inside a pipeline's Build stage,&lt;br&gt;
&lt;code&gt;CodePipeline&lt;/code&gt; is the correct source provider — the pipeline's own Source stage connects to GitHub and hands this project an artifact; the project itself shouldn't also be configured to fetch from GitHub independently.&lt;/p&gt;

&lt;p&gt;If you see "GitHub" instead and the build is working anyway, that's&lt;br&gt;
because CodePipeline overrides a project's own source settings at&lt;br&gt;
invocation time when it's used as a pipeline action — so it was likely still functioning correctly, just configured in a way that's only correct &lt;em&gt;because&lt;/em&gt; of that override, rather than being correct on its own.&lt;/p&gt;

&lt;p&gt;Worth fixing for clarity even if nothing was actually broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;permission denied while trying to connect to the Docker API at&lt;br&gt;
unix:///var/run/docker.sock&lt;/code&gt;&lt;/strong&gt; Local machine issue, nothing to do with this project. Your user isn't in the &lt;code&gt;docker&lt;/code&gt; group. Fix: &lt;code&gt;sudo usermod -aG docker $USER&lt;/code&gt;, then fully log out and back in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;$AWS_ACCOUNT_ID&lt;/code&gt;, &lt;code&gt;$ECR_REPO_NAME&lt;/code&gt;, &lt;code&gt;$CONTAINER_NAME&lt;/code&gt; come back empty in the build&lt;/strong&gt;&lt;br&gt;
If you built the CodeBuild project by hand in the console, these have to be added manually as environment variables on the project — they don't exist by default. See Step 9, which includes &lt;code&gt;AWS_REGION&lt;/code&gt; too — it's technically a CodeBuild built-in, but it didn't reliably resolve in practice, so it's set explicitly here rather than assumed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ECR login or push fails with a malformed URL (something like&lt;br&gt;
&lt;code&gt;....dkr.ecr..amazonaws.com&lt;/code&gt; with a missing region segment, or a stray&lt;code&gt;://&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
This is &lt;code&gt;$AWS_REGION&lt;/code&gt; being empty at the point the buildspec builds the registry URL string — the variable substitution doesn't fail loudly; it just silently produces a broken URL. Fix: make sure &lt;code&gt;AWS_REGION&lt;/code&gt; is set as an explicit environment variable on the CodeBuild project (Step 9), not left to CodeBuild's built-in one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build fails trying to find a Dockerfile, or &lt;code&gt;index.html&lt;/code&gt; isn't found&lt;/strong&gt;&lt;br&gt;
Two separate things can cause this; check both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The sample app file in this repo is named &lt;code&gt;Dockerfile&lt;/code&gt;, not    &lt;code&gt;Dockerfile.sample&lt;/code&gt; — Docker looks for a file literally named    &lt;code&gt;Dockerfile&lt;/code&gt; unless you pass &lt;code&gt;-f&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The buildspec needs to actually be looking in the right folder. This project keeps &lt;code&gt;pipeline-config/&lt;/code&gt; as a subfolder in the repo rather than flattening it to the root (see Prerequisites), and the buildspec &lt;code&gt;cd&lt;/code&gt;s into &lt;code&gt;pipeline-config&lt;/code&gt; before running &lt;code&gt;docker build .&lt;/code&gt; — if you'd flattened the repo instead, or edited the buildspec without keeping that &lt;code&gt;cd&lt;/code&gt; in place, the build looks in the wrong directory and can't find either file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;CodePipeline's Deploy stage fails saying it can't find&lt;br&gt;
&lt;code&gt;imagedefinitions.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The buildspec builds the Docker image from inside &lt;code&gt;pipeline-config/&lt;/code&gt;, but CodePipeline's Deploy stage expects &lt;code&gt;imagedefinitions.json&lt;/code&gt; at the repo root, not buried in that subfolder. The buildspec handles this by &lt;code&gt;cd&lt;/code&gt;ing back to &lt;code&gt;$CODEBUILD_SRC_DIR&lt;/code&gt; (the repo root) before writing the file — if&lt;br&gt;
that line gets removed, or the file ends up written while still inside &lt;code&gt;pipeline-config/&lt;/code&gt;, the artifact step silently packages the wrong path and the Deploy stage can't find it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build succeeds, &lt;code&gt;docker push&lt;/code&gt; fails with a permission error&lt;/strong&gt;&lt;br&gt;
This happens when the CodeBuild service role only has logging and basic artifact access, not ECR — which is exactly what you get if you let the console auto-generate a role for you when creating the project. This guide avoids that: Step 7 has you build &lt;code&gt;bluegreen-demo-codebuild-role&lt;/code&gt; with the full policy attached before the CodeBuild project exists, and Step 9 just selects it as an existing role. If you're hitting this error, check that the role actually has the inline policy from Step 7 attached — it's possible to have skipped that, or to have let CodeBuild generate its own role by choosing "New service role" in Step 9 instead of "Existing service role." This is also already handled correctly in the CloudFormation template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task starts then dies: &lt;code&gt;ResourceInitializationError: unable to pull secrets or registry auth ... dial tcp ... i/o timeout&lt;/code&gt;, sometimes followed by a longer version mentioning &lt;code&gt;GetAuthorizationToken&lt;/code&gt; and an IP address timing out on port 443&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This looks like it's about Secrets Manager or SSM Parameter Store — it isn't, at least not in this project, since nothing here uses either one.&lt;/p&gt;

&lt;p&gt;What's actually happening: the task is trying to reach ECR over HTTPS to authenticate and pull the image, and something is blocking that specific outbound connection. In every case this came up while building this project, the cause was the same: &lt;code&gt;bluegreen-demo-svc-sg&lt;/code&gt;'s outbound rules had been narrowed down to only port 80 (some AWS accounts don't give new security groups an automatic allow-all outbound rule, so it's possible to end up here even without editing anything by hand). Fix: add an outbound rule allowing HTTPS (port 443) to &lt;code&gt;0.0.0.0/0&lt;/code&gt; on that security group. See Step 4 — the CloudFormation template sets this explicitly for exactly&lt;br&gt;
this reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to add CloudWatch Logs permissions to the task execution role?&lt;/strong&gt; No — &lt;code&gt;AmazonECSTaskExecutionRolePolicy&lt;/code&gt; (the managed policy attached to &lt;code&gt;bluegreen-demo-task-exec-role&lt;/code&gt;) already includes &lt;code&gt;logs:CreateLogStream&lt;/code&gt; and &lt;code&gt;logs:PutLogEvents&lt;/code&gt;, which is all a running task needs. The log group itself is created once, ahead of time — by CloudFormation as a stack resource in the automated version, or by the ECS console automatically when you configure logging during task definition creation in the manual version. If logs still aren't showing up, it's almost always the network issue above (the task never got far enough to start shipping logs), not a permissions gap on this role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional: permanently delete task definition revisions, not just&lt;br&gt;
deregister them&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecs list-task-definitions &lt;span class="nt"&gt;--family-prefix&lt;/span&gt; bluegreen-demo-task &lt;span class="nt"&gt;--status&lt;/span&gt; INACTIVE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'taskDefinitionArns'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text | &lt;span class="se"&gt;\&lt;/span&gt;
  xargs &lt;span class="nt"&gt;-n1&lt;/span&gt; aws ecs delete-task-definitions &lt;span class="nt"&gt;--task-definitions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A revision has to be deregistered (inactive) before this will work on it — &lt;code&gt;delete-stack&lt;/code&gt; and the manual teardown steps above both deregister them as part of removing the &lt;code&gt;TaskDefinition&lt;/code&gt; resource, but leave the actual permanent delete as this optional last step, since AWS keeps deregistered revisions around by default in case you want them back.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ecs</category>
      <category>bluegreen</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
