ECS Fargate: Serverless Containers
Fargate runs containers without EC2 instances to manage. You define CPU/memory, AWS handles the rest.
Task Definition
{
"family": "api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "api",
"image": "123456789.dkr.ecr.us-east-1.amazonaws.com/api:latest",
"portMappings": [
{ "containerPort": 3000, "protocol": "tcp" }
],
"environment": [
{ "name": "NODE_ENV", "value": "production" },
{ "name": "PORT", "value": "3000" }
],
"secrets": [
{ "name": "DATABASE_URL", "valueFrom": "arn:aws:ssm:us-east-1:123:parameter/myapp/prod/database-url" },
{ "name": "JWT_SECRET", "valueFrom": "arn:aws:secretsmanager:us-east-1:123:secret:myapp/jwt-secret" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/api",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 10,
"retries": 3,
"startPeriod": 60
}
}
]
}
Service Definition with Auto-Scaling
# Create service
aws ecs create-service --cluster production --service-name api --task-definition api:5 --desired-count 3 --launch-type FARGATE --network-configuration "awsvpcConfiguration={
subnets=[subnet-xxx,subnet-yyy],
securityGroups=[sg-xxx],
assignPublicIp=DISABLED
}" --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=api,containerPort=3000" --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100,deploymentCircuitBreaker={enable=true,rollback=true}"
// Auto-scaling policy
{
"scalableDimension": "ecs:service:DesiredCount",
"serviceNamespace": "ecs",
"resourceId": "service/production/api",
"minCapacity": 2,
"maxCapacity": 20,
"targetTrackingScalingPolicies": [
{
"targetValue": 70,
"predefinedMetricSpecification": {
"predefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"scaleInCooldown": 300,
"scaleOutCooldown": 60
},
{
"targetValue": 1000,
"predefinedMetricSpecification": {
"predefinedMetricType": "ALBRequestCountPerTarget",
"resourceLabel": "app/prod-alb/xxx/targetgroup/api/xxx"
}
}
]
}
ECR Push Script
#!/bin/bash
set -e
AWS_REGION=us-east-1
AWS_ACCOUNT=123456789
ECR_REPO="${AWS_ACCOUNT}.dkr.ecr.${AWS_REGION}.amazonaws.com/api"
IMAGE_TAG="${GITHUB_SHA:-latest}"
# Login to ECR
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com
# Build and push
docker build -t $ECR_REPO:$IMAGE_TAG .
docker push $ECR_REPO:$IMAGE_TAG
# Tag as latest
docker tag $ECR_REPO:$IMAGE_TAG $ECR_REPO:latest
docker push $ECR_REPO:latest
# Update ECS service
aws ecs update-service --cluster production --service api --force-new-deployment --task-definition api
Secrets Management
# Store secrets in SSM Parameter Store
aws ssm put-parameter --name /myapp/prod/database-url --value "postgresql://user:pass@rds-endpoint/myapp" --type SecureString --key-id alias/myapp-key
# IAM policy for ECS task to read secrets
{
"Effect": "Allow",
"Action": ["ssm:GetParameter", "ssm:GetParameters"],
"Resource": "arn:aws:ssm:us-east-1:123:parameter/myapp/prod/*"
}
CloudWatch Logs Insights
# Find errors in last hour
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 100
# P99 response time from structured logs
fields @timestamp, duration
| filter statusCode >= 200
| stats pct(duration, 99) as p99 by bin(5m)