Use Cases

Claude Code × AWS ECS/Fargate Complete Guide | Automate Container Deployments

Automate AWS ECS/Fargate deployments with Claude Code. From task definitions and service configuration to Blue/Green deployments and CDK infrastructure — based on Masa's real-world experience.

“I want to run containers on AWS, but ECS configuration is way too complex” — this is the wall many engineers hit first. Task definitions, services, clusters, load balancers, auto scaling… there are so many settings it’s hard to know where to start.

I’ve been building serverless container environments with ECS/Fargate at work, and since I started using Claude Code to generate everything from task definitions to CDK code just by describing my architecture, deployments have become dramatically easier. This article walks through the practical steps.


ECS/Fargate Basics in 3 Minutes

Cluster:         The "box" where ECS containers run
Task Definition: Container specification (image, CPU, memory, env vars)
Service:         Defines how many tasks to run (includes auto scaling)
Fargate:         Serverless container execution engine — no server management needed

Unlike EC2, Fargate requires no server provisioning or patch management. You can focus entirely on container configuration.


Step 1: Auto-Generate a Task Definition

claude -p "
Generate an ECS task definition in JSON with the following requirements:

- App: Node.js API (port 3000)
- CPU: 512 (0.5 vCPU)
- Memory: 1024 MB
- Environment variables: DATABASE_URL (retrieved from Secrets Manager), NODE_ENV=production
- Logging: CloudWatch Logs (log group /ecs/myapp)
- Health check: /health endpoint, 30-second interval
- Container image: 123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest
- Execution role: ecsTaskExecutionRole
- Task role: myapp-task-role (access to DynamoDB and S3)
"

Generated task definition:

{
  "family": "myapp-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789:role/myapp-task-role",
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest",
      "portMappings": [{ "containerPort": 3000, "protocol": "tcp" }],
      "environment": [{ "name": "NODE_ENV", "value": "production" }],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789:secret:prod/database-url"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}

Step 2: Build ECS/Fargate Infrastructure with CDK

Beyond just a task definition, we’ll generate the cluster, service, and ALB all at once using CDK.

claude -p "
Implement the following ECS/Fargate environment in CDK TypeScript in lib/ecs-stack.ts:

- VPC: Import an existing VPC (retrieve vpcId from environment variable)
- ECS Cluster: Fargate only
- Task Definition: Same spec as generated above
- ALB: HTTPS (retrieve ACM certificate ARN from environment variable), HTTP redirects to HTTPS with 301
- Service: Auto scaling with minimum 2, maximum 10 tasks
  - Scale out at 70% CPU utilization
  - Target tracking scaling
- Blue/Green deploy support (CodeDeploy integration)
- Display service health on a CloudWatch dashboard
"
// lib/ecs-stack.ts
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ecsp from "aws-cdk-lib/aws-ecs-patterns";
import * as acm from "aws-cdk-lib/aws-certificatemanager";

export class EcsStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Import existing VPC
    const vpc = ec2.Vpc.fromLookup(this, "Vpc", {
      vpcId: process.env.VPC_ID!,
    });

    // ECS Cluster
    const cluster = new ecs.Cluster(this, "Cluster", {
      vpc,
      clusterName: "myapp-cluster",
      containerInsights: true,  // Enable CloudWatch Container Insights
    });

    // ACM Certificate
    const certificate = acm.Certificate.fromCertificateArn(
      this, "Certificate",
      process.env.CERTIFICATE_ARN!
    );

    // Task Definition
    const taskDef = new ecs.FargateTaskDefinition(this, "TaskDef", {
      cpu: 512,
      memoryLimitMiB: 1024,
    });

    const container = taskDef.addContainer("app", {
      image: ecs.ContainerImage.fromEcrRepository(
        ecr.Repository.fromRepositoryName(this, "Repo", "myapp")
      ),
      environment: { NODE_ENV: "production" },
      logging: ecs.LogDrivers.awsLogs({ streamPrefix: "ecs" }),
      healthCheck: {
        command: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        interval: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(5),
        retries: 3,
        startPeriod: cdk.Duration.seconds(60),
      },
    });
    container.addPortMappings({ containerPort: 3000 });

    // ALB + Fargate Service (concise using patterns)
    const service = new ecsp.ApplicationLoadBalancedFargateService(
      this, "Service", {
        cluster,
        taskDefinition: taskDef,
        desiredCount: 2,
        certificate,
        redirectHTTP: true,
        publicLoadBalancer: true,
      }
    );

    // Auto Scaling configuration
    const scaling = service.service.autoScaleTaskCount({
      minCapacity: 2,
      maxCapacity: 10,
    });
    scaling.scaleOnCpuUtilization("CpuScaling", {
      targetUtilizationPercent: 70,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });

    // Output
    new cdk.CfnOutput(this, "LoadBalancerDns", {
      value: service.loadBalancer.loadBalancerDnsName,
    });
  }
}

Step 3: Build a CI/CD Pipeline with GitHub Actions

claude -p "
Create the following CI/CD pipeline with GitHub Actions:

1. Trigger on push to main branch
2. Build Docker image and push to ECR
3. Update the image tag in the ECS task definition to the new SHA
4. Deploy the new task definition to the ECS service
5. Notify Slack on successful deployment

Environment: ap-northeast-1, ECR repository name: myapp
"
name: Deploy to ECS

on:
  push:
    branches: [main]

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: myapp
  ECS_CLUSTER: myapp-cluster
  ECS_SERVICE: myapp-service
  CONTAINER_NAME: app

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image to ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

      - name: Download task definition
        run: |
          aws ecs describe-task-definition --task-definition myapp-task \
            --query taskDefinition > task-definition.json

      - name: Update ECS task definition with new image
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy to ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

      - name: Notify Slack on success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {"text": "✅ Deployed to ECS: ${{ github.sha }}"}

Step 4: Configure Blue/Green Deployment

claude -p "
Generate CDK code and an AppSpec file to set up Blue/Green deployment (CodeDeploy) for an ECS service.
Rollback condition: automatically roll back if a CloudWatch alarm fires within 5 minutes of deployment.
"
# appspec.yaml (for CodeDeploy)
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: app
          ContainerPort: 3000
        PlatformVersion: LATEST
Hooks:
  - BeforeAllowTraffic: "arn:aws:lambda:ap-northeast-1:123456789:function:health-check"
  - AfterAllowTraffic: "arn:aws:lambda:ap-northeast-1:123456789:function:smoke-test"

Top 5 Pitfalls

1. Forgetting startPeriod in the health check

Fargate containers run health checks immediately on startup, but if your application is slow to start, it instantly becomes UNHEALTHY. Set startPeriod: 60 (seconds) to give your app time to initialize.

2. Confusing the task role and execution role

executionRole (ecsTaskExecutionRole): Permissions needed to start the container
  → Pull image from ECR, write to CloudWatch Logs

taskRole (myapp-task-role): Permissions used by the application
  → Access to DynamoDB, S3, SQS, etc.

It’s very easy to add permissions to the wrong role.

3. Security group configuration in awsvpc network mode

Fargate requires the awsvpc network mode. If you forget to restrict the container’s security group to allow traffic only from the ALB, the container will be directly exposed to the internet.

4. Execution role required to fetch from Secrets Manager

To retrieve Secrets Manager values via secrets, the executionRole must have secretsmanager:GetSecretValue permission. Missing this permission is a very common reason containers fail to start.

5. The desired_count: 0 problem during deployment

A service with a minimum of 0 in auto scaling will have all containers shut down overnight, causing slow restarts in the morning. Set the minimum to at least 2 in production.


Summary

TaskClaude Code’s Contribution
Task definition generationComplete JSON from requirements alone
CDK implementationCluster, service, ALB, auto scaling generated together
CI/CD setupGitHub Actions workflow generation
Blue/Green deploymentAuto-generates AppSpec and CodeDeploy config
TroubleshootingIdentifies root cause and fixes from error logs

ECS has many configuration items, but by telling Claude Code “I want this kind of setup,” you get a complete, best-practice configuration. Starting with CDK is the easiest and most recommended approach.

References

#claude-code #aws #ecs #fargate #container #devops

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Just enter your email and we'll send you the single-page A4 cheatsheet right away.

We handle your data with care and never send spam.

Masa

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.