AWS ECS — Container Orchestration with Fargate Complete Guide

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

Why This Matters

AWS ECS (Elastic Container Service) provides managed container orchestration without the operational complexity of Kubernetes. With Fargate, you run containers without managing EC2 instances — AWS provisions and scales the underlying compute automatically. ECS integrates natively with ALB, ECR, IAM, CloudWatch, and Secrets Manager, making it the pragmatic choice for teams that want container orchestration without the Kubernetes learning curve.

Core Concepts

  • Cluster: Logical group of tasks/services. With Fargate, no EC2 instances to manage.
  • Task Definition: Blueprint describing containers (image, CPU, memory, env vars, ports).
  • Task: Running instance of a Task Definition.
  • Service: Maintains desired count of tasks, handles rolling updates, integrates with load balancers.

Task Definitions

{
  "family": "my-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789012:role/myapp-task-role",
  "containerDefinitions": [
    {
      "name": "my-app",
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.2.3",
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {"name": "NODE_ENV", "value": "production"},
        {"name": "PORT", "value": "3000"}
      ],
      "secrets": [
        {
          "name": "DB_PASSWORD",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/db-password"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-app",
          "awslogs-region": "us-east-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
      },
      "essential": true
    }
  ]
}

Creating Clusters and Services

# Create Fargate cluster
aws ecs create-cluster \
  --cluster-name production \
  --capacity-providers FARGATE FARGATE_SPOT \
  --default-capacity-provider-strategy \
    capacityProvider=FARGATE,weight=1 \
    capacityProvider=FARGATE_SPOT,weight=4
 
# Register task definition
aws ecs register-task-definition \
  --cli-input-json file://task-definition.json
 
# Create service with load balancer
aws ecs create-service \
  --cluster production \
  --service-name my-app \
  --task-definition my-app:1 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-private-1a", "subnet-private-1b"],
      "securityGroups": ["sg-app"],
      "assignPublicIp": "DISABLED"
    }
  }' \
  --load-balancers '[{
    "targetGroupArn": "arn:aws:elasticloadbalancing:...",
    "containerName": "my-app",
    "containerPort": 3000
  }]' \
  --deployment-configuration '{
    "minimumHealthyPercent": 100,
    "maximumPercent": 200
  }'
 
# Update service (rolling deployment)
aws ecs update-service \
  --cluster production \
  --service my-app \
  --task-definition my-app:2 \
  --force-new-deployment

IAM Roles

# Task Execution Role — allows ECS to pull images and write logs
aws iam create-role \
  --role-name ecsTaskExecutionRole \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "ecs-tasks.amazonaws.com"},
      "Action": "sts:AssumeRole"
    }]
  }'
 
aws iam attach-role-policy \
  --role-name ecsTaskExecutionRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
 
# Grant access to specific Secrets Manager secrets
aws iam put-role-policy \
  --role-name ecsTaskExecutionRole \
  --policy-name SecretsAccess \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/*"
    }]
  }'

Auto Scaling

# Register scalable target
aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/production/my-app \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 \
  --max-capacity 20
 
# Scale on CPU utilization
aws application-autoscaling put-scaling-policy \
  --policy-name cpu-scaling \
  --service-namespace ecs \
  --resource-id service/production/my-app \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
    },
    "TargetValue": 70.0,
    "ScaleInCooldown": 300,
    "ScaleOutCooldown": 60
  }'

CI/CD Integration

# Build and push to ECR in CI pipeline
AWS_ACCOUNT=123456789012
REGION=us-east-1
ECR_REPO=$AWS_ACCOUNT.dkr.ecr.$REGION.amazonaws.com/my-app
IMAGE_TAG=$(git rev-parse --short HEAD)
 
# Authenticate with ECR
aws ecr get-login-password --region $REGION | \
  docker login --username AWS --password-stdin $ECR_REPO
 
# Build, tag, push
docker build -t $ECR_REPO:$IMAGE_TAG .
docker tag $ECR_REPO:$IMAGE_TAG $ECR_REPO:latest
docker push $ECR_REPO:$IMAGE_TAG
docker push $ECR_REPO:latest
 
# Update ECS service with new image
aws ecs update-service \
  --cluster production \
  --service my-app \
  --force-new-deployment

Common Mistakes

  • Not configuring health checks on containers — ECS cannot determine when a task is ready to serve traffic
  • Using the same IAM role for task execution and task role — separate concerns: execution role pulls images, task role accesses AWS services
  • Setting assignPublicIp: ENABLED for tasks in private subnets — tasks need a NAT Gateway to reach ECR and other AWS services
  • Not setting minimumHealthyPercent: 100 for stateful services — allows ECS to terminate healthy tasks before replacements are running
  • Ignoring CloudWatch Container Insights — without it, debugging container-level CPU/memory issues requires guesswork

Best Practices

  • Use Fargate Spot for non-critical workloads (batch, dev environments) — up to 70% cost savings
  • Store secrets in AWS Secrets Manager and reference them as secrets in task definitions — never pass as environment variables
  • Use ECR lifecycle policies to clean up old images and reduce storage costs
  • Enable Container Insights for cluster-level and service-level CloudWatch metrics
  • Tag task definitions with application version for easy rollback identification
  • Use Capacity Provider strategies mixing Fargate (base) and Fargate Spot (burst) for cost optimization

Key Takeaways

  • ECS is AWS-native container orchestration — simpler than Kubernetes for teams already invested in the AWS ecosystem
  • Fargate eliminates EC2 management — you pay per vCPU/memory/second for actual task runtime only
  • Task Definitions are immutable blueprints — each update creates a new revision, enabling easy rollbacks
  • The Task Execution Role allows ECS infrastructure to pull ECR images and write CloudWatch logs
  • The Task Role is assigned to your containers to call AWS APIs — follow least-privilege with separate per-service roles
  • Service auto scaling uses Application Auto Scaling with target tracking or step scaling policies
  • Rolling deployments use minimumHealthyPercent and maximumPercent to control how many old/new tasks run simultaneously
  • Fargate Spot tasks can be interrupted with 2-minute notice — suitable for batch workloads, not for user-facing services

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading