ECS Fargate: Serverless Containers
Deploy containers to AWS ECS with Fargate. No servers to manage, automatic scaling, and production-ready infrastructure.
What you'll accomplish
Your application running on ECS Fargate with load balancing and auto-scaling
Prerequisites
- AWS account with CLI configured
- Docker installed locally
- Basic container knowledge
What is ECS Fargate?
ECS (Elastic Container Service) is AWS's container orchestration service. Fargate is a serverless compute engine for containers - you don't manage any servers.
Traditional VPS Lightsail + Docker ECS Fargate
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ You manage: │ │ You manage: │ │ You manage: │
│ - OS updates │ │ - OS updates │ │ - Containers │
│ - Docker │ │ - Docker │ │ │
│ - Scaling │ │ - Scaling │ │ AWS manages: │
│ - Load balancer │ │ - Container │ │ - Everything │
│ - Container │ │ │ │ else │
└─────────────────┘ └─────────────────┘ └─────────────────┘
When to Use ECS Fargate
Best for:
- Production applications needing high availability
- Auto-scaling based on traffic
- Teams who don't want to manage servers
- Microservices architectures
Consider alternatives if:
- Simple app with steady traffic → Lightsail
- Need to minimize costs → EC2 instances
- Want even simpler deployment → App Runner
Note
ECS Fargate costs more than running your own servers, but saves significant operations time. The trade-off is worth it for many teams.
Core Concepts
Before we begin, understand these ECS terms:
| Concept | What It Is | Analogy |
|---|---|---|
| Cluster | Group of services/tasks | A data center |
| Task Definition | Container blueprint | A Dockerfile on steroids |
| Task | Running instance of task definition | A running container |
| Service | Manages tasks, keeps them running | PM2 or systemd |
| Container Registry (ECR) | Where images are stored | Docker Hub, but private |
Step 1: Set Up ECR (Container Registry)
First, create a place to store your Docker images.
1.1 Create ECR Repository
1.2 Build and Push Your Image
bash# Get your AWS account ID AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) REGION="us-east-1" # Login to ECR aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com # Build your image docker build -t myapp . # Tag for ECR docker tag myapp:latest $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/myapp:latest # Push to ECR docker push $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/myapp:latest
Note
ECR login tokens expire after 12 hours. Re-run the login command if you get authentication errors.
Step 2: Create an ECS Cluster
A cluster is where your containers will run.
Using AWS Console (Visual Method)
- Go to ECS Console
- Click Create cluster
- Enter cluster name:
myapp-cluster - Under Infrastructure, select AWS Fargate (serverless)
- Click Create
Using AWS CLI
bashaws ecs create-cluster \ --cluster-name myapp-cluster \ --capacity-providers FARGATE FARGATE_SPOT \ --default-capacity-provider-strategy \ capacityProvider=FARGATE,weight=1 \ capacityProvider=FARGATE_SPOT,weight=1
Note
FARGATE_SPOT uses spare AWS capacity at up to 70% discount. Great for non-critical workloads or development environments.
Step 3: Create a Task Definition
The task definition tells ECS how to run your container.
3.1 Create Task Definition File
Create task-definition.json:
json{ "family": "myapp", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole", "containerDefinitions": [ { "name": "myapp", "image": "YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/myapp:latest", "essential": true, "portMappings": [ { "containerPort": 3000, "protocol": "tcp" } ], "environment": [ { "name": "NODE_ENV", "value": "production" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/myapp", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "ecs" } }, "healthCheck": { "command": ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 } } ] }
3.2 Create the IAM Role
ECS needs a role to pull images and write logs:
bash# Create trust policy cat > trust-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF # Create the role aws iam create-role \ --role-name ecsTaskExecutionRole \ --assume-role-policy-document file://trust-policy.json # Attach the AWS managed policy aws iam attach-role-policy \ --role-name ecsTaskExecutionRole \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
3.3 Create CloudWatch Log Group
bashaws logs create-log-group --log-group-name /ecs/myapp --region us-east-1
3.4 Register Task Definition
bashaws ecs register-task-definition --cli-input-json file://task-definition.json
Step 4: Set Up Networking
ECS Fargate requires a VPC with subnets and security groups.
4.1 Find Your VPC and Subnets
bash# Get default VPC aws ec2 describe-vpcs \ --filters "Name=isDefault,Values=true" \ --query "Vpcs[0].VpcId" \ --output text # Get subnets in the VPC aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=YOUR_VPC_ID" \ --query "Subnets[*].SubnetId" \ --output text
4.2 Create a Security Group
bash# Create security group aws ec2 create-security-group \ --group-name myapp-ecs-sg \ --description "Security group for myapp ECS tasks" \ --vpc-id YOUR_VPC_ID # Allow inbound traffic on your app port aws ec2 authorize-security-group-ingress \ --group-id YOUR_SECURITY_GROUP_ID \ --protocol tcp \ --port 3000 \ --cidr 0.0.0.0/0 # Allow inbound HTTP aws ec2 authorize-security-group-ingress \ --group-id YOUR_SECURITY_GROUP_ID \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0 # Allow inbound HTTPS aws ec2 authorize-security-group-ingress \ --group-id YOUR_SECURITY_GROUP_ID \ --protocol tcp \ --port 443 \ --cidr 0.0.0.0/0
Step 5: Create an Application Load Balancer
For production, use a load balancer to distribute traffic.
5.1 Create the Load Balancer
bash# Create ALB aws elbv2 create-load-balancer \ --name myapp-alb \ --subnets subnet-xxx subnet-yyy \ --security-groups YOUR_SECURITY_GROUP_ID \ --scheme internet-facing \ --type application
5.2 Create Target Group
bashaws elbv2 create-target-group \ --name myapp-tg \ --protocol HTTP \ --port 3000 \ --vpc-id YOUR_VPC_ID \ --target-type ip \ --health-check-path /api/health \ --health-check-interval-seconds 30
5.3 Create Listener
bashaws elbv2 create-listener \ --load-balancer-arn YOUR_ALB_ARN \ --protocol HTTP \ --port 80 \ --default-actions Type=forward,TargetGroupArn=YOUR_TARGET_GROUP_ARN
Step 6: Create an ECS Service
Now tie everything together with a service:
bashaws ecs create-service \ --cluster myapp-cluster \ --service-name myapp-service \ --task-definition myapp \ --desired-count 2 \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[YOUR_SECURITY_GROUP_ID],assignPublicIp=ENABLED}" \ --load-balancers "targetGroupArn=YOUR_TARGET_GROUP_ARN,containerName=myapp,containerPort=3000"
Note
desired-count: 2 runs two copies for high availability. Even if one crashes, your app stays up.
Step 7: Verify Deployment
Check Service Status
bash# View service aws ecs describe-services \ --cluster myapp-cluster \ --services myapp-service # View running tasks aws ecs list-tasks --cluster myapp-cluster --service-name myapp-service
Check Load Balancer
bash# Get ALB DNS name aws elbv2 describe-load-balancers \ --names myapp-alb \ --query "LoadBalancers[0].DNSName" \ --output text # Test your app curl http://myapp-alb-xxxxx.us-east-1.elb.amazonaws.com/api/health
View Logs
bash# Via CLI aws logs tail /ecs/myapp --follow # Or in console: CloudWatch → Log groups → /ecs/myapp
Step 8: Configure Auto Scaling
Scale automatically based on CPU or request count.
8.1 Register Scalable Target
bashaws application-autoscaling register-scalable-target \ --service-namespace ecs \ --resource-id service/myapp-cluster/myapp-service \ --scalable-dimension ecs:service:DesiredCount \ --min-capacity 1 \ --max-capacity 10
8.2 Create Scaling Policy
bash# Scale based on CPU aws application-autoscaling put-scaling-policy \ --service-namespace ecs \ --resource-id service/myapp-cluster/myapp-service \ --scalable-dimension ecs:service:DesiredCount \ --policy-name myapp-cpu-scaling \ --policy-type TargetTrackingScaling \ --target-tracking-scaling-policy-configuration '{ "TargetValue": 70.0, "PredefinedMetricSpecification": { "PredefinedMetricType": "ECSServiceAverageCPUUtilization" }, "ScaleOutCooldown": 60, "ScaleInCooldown": 60 }'
This keeps CPU around 70% by adding or removing tasks.
Step 9: Deploy Updates
9.1 Update Script
Create deploy-ecs.sh:
bash#!/bin/bash set -e CLUSTER="myapp-cluster" SERVICE="myapp-service" AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) REGION="us-east-1" IMAGE_TAG=${1:-latest} echo "Building and pushing image..." docker build -t myapp . docker tag myapp:latest $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/myapp:$IMAGE_TAG aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com docker push $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/myapp:$IMAGE_TAG echo "Updating ECS service..." aws ecs update-service \ --cluster $CLUSTER \ --service $SERVICE \ --force-new-deployment echo "Waiting for deployment to complete..." aws ecs wait services-stable --cluster $CLUSTER --services $SERVICE echo "Deployment complete!"
9.2 Zero-Downtime Deployments
ECS handles rolling updates automatically:
- Starts new tasks with the new image
- Waits for health checks to pass
- Drains connections from old tasks
- Stops old tasks
Configure deployment settings in your service:
bashaws ecs update-service \ --cluster myapp-cluster \ --service myapp-service \ --deployment-configuration "minimumHealthyPercent=50,maximumPercent=200"
Step 10: Add HTTPS with ACM
10.1 Request Certificate
bashaws acm request-certificate \ --domain-name yourdomain.com \ --validation-method DNS \ --subject-alternative-names "*.yourdomain.com"
10.2 Add DNS Validation
Follow the instructions in ACM console to add CNAME records to your DNS.
10.3 Create HTTPS Listener
bashaws elbv2 create-listener \ --load-balancer-arn YOUR_ALB_ARN \ --protocol HTTPS \ --port 443 \ --certificates CertificateArn=YOUR_CERTIFICATE_ARN \ --default-actions Type=forward,TargetGroupArn=YOUR_TARGET_GROUP_ARN # Redirect HTTP to HTTPS aws elbv2 modify-listener \ --listener-arn YOUR_HTTP_LISTENER_ARN \ --default-actions '[{"Type":"redirect","RedirectConfig":{"Protocol":"HTTPS","Port":"443","StatusCode":"HTTP_301"}}]'
Using AWS Copilot (Simpler Alternative)
AWS Copilot simplifies ECS deployment significantly:
bash# Install Copilot brew install aws/tap/copilot-cli # macOS # or download from GitHub releases # Initialize application copilot init # Deploy to production copilot deploy # View logs copilot svc logs # Scale copilot svc update --count 3
Copilot creates all the infrastructure (VPC, subnets, ALB, ECS) automatically.
Cost Breakdown
| Component | Monthly Cost (example) |
|---|---|
| ECS Fargate (2 tasks, 0.25 vCPU, 0.5GB) | ~$15/month |
| Application Load Balancer | ~$16/month |
| ECR (1GB storage) | ~$0.10/month |
| Data Transfer (10GB) | ~$0.90/month |
| CloudWatch Logs (1GB) | ~$0.50/month |
| Total | ~$32-35/month |
Note
Use FARGATE_SPOT for development environments to save up to 70%.
Troubleshooting
Tasks Keep Failing
bash# Check stopped tasks aws ecs describe-tasks \ --cluster myapp-cluster \ --tasks $(aws ecs list-tasks --cluster myapp-cluster --desired-status STOPPED --query 'taskArns[0]' --output text) # Check logs aws logs tail /ecs/myapp --since 1h
Health Check Failures
- Ensure your
/api/healthendpoint returns 200 - Check security group allows traffic on your port
- Verify the container is listening on 0.0.0.0, not 127.0.0.1
Image Pull Errors
bash# Verify ECR login aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com # Check image exists aws ecr describe-images --repository-name myapp
Service Won't Stabilize
- Check task definition memory/CPU limits
- Verify IAM role permissions
- Look at CloudWatch logs for errors
What's Next?
You now have a production-ready container deployment on AWS. For custom domains and professional URLs, continue to the Route 53 lesson.
Note
Checkpoint: Your application should be accessible at your ALB's DNS name: http://myapp-alb-xxxxx.us-east-1.elb.amazonaws.com