2026-01-27 17:35:45 -05:00
---
name : aws-ecs-fargate
description : Deploy containers on ECS and Fargate. Configure task definitions, services, and load balancing. Use when running containerized workloads on AWS.
license : MIT
metadata :
author : devops-skills
version : "1.0"
---
# AWS ECS & Fargate
2026-03-24 18:02:50 -04:00
Run containerized applications on Amazon ECS with Fargate serverless compute or EC2 launch type.
## When to Use This Skill
- Deploying Docker containers to AWS without managing servers (Fargate)
- Running microservices with service discovery and load balancing
- Setting up blue/green or rolling deployments for containerized apps
- Configuring auto-scaling for container workloads
- Migrating from docker-compose or Kubernetes to ECS
- Troubleshooting task failures, health check issues, or networking problems
## Prerequisites
- AWS CLI v2 installed and configured
- Docker installed for building and pushing images
- IAM permissions: `ecs:*` , `ecr:*` , `elasticloadbalancing:*` , `logs:*` , `iam:PassRole`
- An ECR repository for storing container images
- A VPC with subnets and an ALB (see [aws-vpc ](../aws-vpc/ ))
## Cluster Setup
```bash
# Create an ECS cluster with Container Insights enabled
aws ecs create-cluster \
--cluster-name production \
--capacity-providers FARGATE FARGATE_SPOT \
--default-capacity-provider-strategy '[
{"capacityProvider": "FARGATE", "weight": 1, "base": 2},
{"capacityProvider": "FARGATE_SPOT", "weight": 3}
]' \
--settings '[{"name": "containerInsights", "value": "enabled"}]'
# List clusters
aws ecs list-clusters
# Describe cluster details
aws ecs describe-clusters --clusters production --include STATISTICS ATTACHMENTS
```
## Push Image to ECR
```bash
# Create ECR repository
aws ecr create-repository \
--repository-name myapp \
--image-scanning-configuration scanOnPush = true \
--encryption-configuration encryptionType = KMS
# Authenticate Docker to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
# Build, tag, and push
docker build -t myapp:latest .
docker tag myapp:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
```
2026-01-27 17:35:45 -05:00
## Task Definition
```json
{
"family" : "myapp" ,
"networkMode" : "awsvpc" ,
"requiresCompatibilities" : [ "FARGATE" ],
2026-03-24 18:02:50 -04:00
"cpu" : "512" ,
"memory" : "1024" ,
"executionRoleArn" : "arn:aws:iam::123456789012:role/ecsTaskExecutionRole" ,
"taskRoleArn" : "arn:aws:iam::123456789012:role/ecsTaskRole" ,
"containerDefinitions" : [
{
"name" : "myapp" ,
"image" : "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest" ,
"essential" : true ,
"portMappings" : [
{
"containerPort" : 8080 ,
"protocol" : "tcp"
}
],
"environment" : [
{ "name" : "NODE_ENV" , "value" : "production" },
{ "name" : "PORT" , "value" : "8080" }
],
"secrets" : [
{
"name" : "DB_PASSWORD" ,
"valueFrom" : "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/db-password"
},
{
"name" : "API_KEY" ,
"valueFrom" : "arn:aws:ssm:us-east-1:123456789012:parameter/myapp/api-key"
}
],
"healthCheck" : {
"command" : [ "CMD-SHELL" , "curl -f http://localhost:8080/health || exit 1" ],
"interval" : 30 ,
"timeout" : 5 ,
"retries" : 3 ,
"startPeriod" : 60
},
"logConfiguration" : {
"logDriver" : "awslogs" ,
"options" : {
"awslogs-group" : "/ecs/myapp" ,
"awslogs-region" : "us-east-1" ,
"awslogs-stream-prefix" : "ecs" ,
"awslogs-create-group" : "true"
}
},
"ulimits" : [
{ "name" : "nofile" , "softLimit" : 65536 , "hardLimit" : 65536 }
]
2026-01-27 17:35:45 -05:00
}
2026-03-24 18:02:50 -04:00
]
2026-01-27 17:35:45 -05:00
}
```
2026-03-24 18:02:50 -04:00
```bash
# Register the task definition
aws ecs register-task-definition --cli-input-json file://task-definition.json
# List task definition revisions
aws ecs list-task-definitions --family-prefix myapp
# Deregister an old revision
aws ecs deregister-task-definition --task-definition myapp:1
```
## Create Service with ALB
2026-01-27 17:35:45 -05:00
```bash
2026-03-24 18:02:50 -04:00
# Create the CloudWatch log group first
aws logs create-log-group --log-group-name /ecs/myapp
aws logs put-retention-policy --log-group-name /ecs/myapp --retention-in-days 30
# Create ECS service with load balancer
2026-01-27 17:35:45 -05:00
aws ecs create-service \
2026-03-24 18:02:50 -04:00
--cluster production \
2026-01-27 17:35:45 -05:00
--service-name myapp \
2026-03-24 18:02:50 -04:00
--task-definition myapp:2 \
--desired-count 3 \
2026-01-27 17:35:45 -05:00
--launch-type FARGATE \
2026-03-24 18:02:50 -04:00
--platform-version LATEST \
--deployment-configuration '{
"deploymentCircuitBreaker": {"enable": true, "rollback": true},
"maximumPercent": 200,
"minimumHealthyPercent": 100
}' \
2026-01-27 17:35:45 -05:00
--network-configuration '{
"awsvpcConfiguration": {
2026-03-24 18:02:50 -04:00
"subnets": ["subnet-private-a", "subnet-private-b"],
"securityGroups": ["sg-app"],
"assignPublicIp": "DISABLED"
2026-01-27 17:35:45 -05:00
}
}' \
--load-balancers '[{
2026-03-24 18:02:50 -04:00
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/myapp-tg/abc123",
2026-01-27 17:35:45 -05:00
"containerName": "myapp",
"containerPort": 8080
2026-03-24 18:02:50 -04:00
}]' \
--service-registries '[{
"registryArn": "arn:aws:servicediscovery:us-east-1:123456789012:service/srv-abc123"
}]' \
--enable-execute-command \
--propagate-tags SERVICE
2026-01-27 17:35:45 -05:00
```
2026-03-24 18:02:50 -04:00
## Deployments
2026-01-27 17:35:45 -05:00
```bash
2026-03-24 18:02:50 -04:00
# Rolling update - update task definition and force new deployment
2026-01-27 17:35:45 -05:00
aws ecs update-service \
2026-03-24 18:02:50 -04:00
--cluster production \
2026-01-27 17:35:45 -05:00
--service myapp \
2026-03-24 18:02:50 -04:00
--task-definition myapp:3 \
2026-01-27 17:35:45 -05:00
--force-new-deployment
2026-03-24 18:02:50 -04:00
# Watch deployment progress
aws ecs describe-services \
--cluster production \
--services myapp \
--query "services[0].deployments[].{Status:status,Running:runningCount,Desired:desiredCount,TaskDef:taskDefinition}" \
--output table
# Wait for service to stabilize
aws ecs wait services-stable --cluster production --services myapp
# Exec into a running container for debugging
aws ecs execute-command \
--cluster production \
--task arn:aws:ecs:us-east-1:123456789012:task/production/abc123 \
--container myapp \
--interactive \
--command "/bin/sh"
2026-01-27 17:35:45 -05:00
```
2026-03-24 18:02:50 -04:00
## Auto Scaling
2026-01-27 17:35:45 -05:00
2026-03-24 18:02:50 -04:00
```bash
# Register ECS service as a scalable target
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/production/myapp \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 2 \
--max-capacity 20
# Target tracking policy - scale on CPU utilization
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/production/myapp \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-target-tracking \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"TargetValue": 70.0,
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60
}'
# Scale on request count per target (ALB)
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/production/myapp \
--scalable-dimension ecs:service:DesiredCount \
--policy-name request-count-tracking \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ALBRequestCountPerTarget",
"ResourceLabel": "app/my-alb/abc123/targetgroup/myapp-tg/def456"
},
"TargetValue": 1000.0,
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60
}'
```
## Terraform ECS Fargate Service
```hcl
resource "aws_ecs_cluster" "main" {
name = "production"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_task_definition" "myapp" {
family = "myapp"
network_mode = "awsvpc"
requires_compatibilities = [ "FARGATE" ]
cpu = 512
memory = 1024
execution_role_arn = aws_iam_role . ecs_execution . arn
task_role_arn = aws_iam_role . ecs_task . arn
container_definitions = jsonencode ([ {
name = "myapp"
image = "${aws_ecr_repository.myapp.repository_url}:latest"
essential = true
portMappings = [ {
containerPort = 8080
protocol = "tcp"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group . myapp . name
awslogs-region = "us-east-1"
awslogs-stream-prefix = "ecs"
}
}
healthCheck = {
command = [ "CMD-SHELL", "curl -f http://localhost:8080/health || exit 1" ]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}])
}
resource "aws_ecs_service" "myapp" {
name = "myapp"
cluster = aws_ecs_cluster . main . id
task_definition = aws_ecs_task_definition . myapp . arn
desired_count = 3
launch_type = "FARGATE"
deployment_circuit_breaker {
enable = true
rollback = true
}
network_configuration {
subnets = aws_subnet . private [ * ]. id
security_groups = [ aws_security_group . app . id ]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group . myapp . arn
container_name = "myapp"
container_port = 8080
}
enable_execute_command = true
propagate_tags = "SERVICE"
}
```
## Viewing Logs
```bash
# Tail logs from CloudWatch
aws logs tail /ecs/myapp --follow --since 1h
# Get logs for a specific task
aws logs get-log-events \
--log-group-name /ecs/myapp \
--log-stream-name "ecs/myapp/abc123def456" \
--start-from-head
# Filter logs for errors
aws logs filter-log-events \
--log-group-name /ecs/myapp \
--filter-pattern "ERROR" \
--start-time $( date -d '1 hour ago' +%s000)
```
## Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Task stuck in PROVISIONING | No available capacity in subnets | Check subnet availability and capacity provider |
| Task fails immediately | Container crashes on startup | Check CloudWatch logs; run image locally first |
| Health check failing | App not ready within startPeriod | Increase `startPeriod` ; verify health endpoint |
| Cannot pull ECR image | Execution role missing ECR permissions | Attach `AmazonECSTaskExecutionRolePolicy` |
| Service stuck at 0 running | Security group blocks ALB health check | Allow ALB SG to reach container port |
| Exec command fails | SSM agent not initialized | Ensure `enableExecuteCommand` is true; check task role |
| High Fargate costs | Not using Fargate Spot for tolerant workloads | Add FARGATE_SPOT capacity provider |
| Container OOM killed | Memory limit too low | Increase `memory` in task definition; check for leaks |
| Slow deployments | minimumHealthyPercent too high | Set to 50% for faster rolling updates |
2026-01-27 17:35:45 -05:00
## Related Skills
2026-03-24 18:02:50 -04:00
- [docker-management ](../../../devops/containers/docker-management/ ) - Container fundamentals
- [container-registries ](../../../devops/containers/container-registries/ ) - ECR and image management
- [aws-vpc ](../aws-vpc/ ) - Networking for ECS tasks
- [aws-iam ](../aws-iam/ ) - Task and execution roles
- [terraform-aws ](../terraform-aws/ ) - Infrastructure as Code deployment