ecs-fargate
SKILL.md
AWS Fargate for ECS
Complete guide to AWS Fargate serverless container compute, including sizing, optimization, and best practices.
Quick Reference
| Aspect | Fargate | EC2 |
|---|---|---|
| Management | Serverless | Manual |
| Scaling | Automatic | ASG-based |
| Pricing | Per vCPU-second | Per instance-hour |
| Overhead | None | High |
| Control | Limited | Full |
| Best For | Variable load | Steady state |
When to Use Fargate
Use Fargate when:
- Team prioritizes operational simplicity
- Variable or unpredictable traffic
- Microservices architecture
- Rapid scaling needed
- Development/testing environments
- No specific instance type requirements
Use EC2 when:
- Steady-state, predictable workloads
- Cost optimization is critical (>60% utilization)
- Need specific instance types (GPU, high memory)
- Existing Reserved Instance commitments
- Custom AMI requirements
CPU and Memory Configurations
Valid Combinations
| CPU (vCPU) | Memory Options |
|---|---|
| 0.25 | 512 MB, 1 GB, 2 GB |
| 0.5 | 1 GB - 4 GB (1 GB increments) |
| 1 | 2 GB - 8 GB (1 GB increments) |
| 2 | 4 GB - 16 GB (1 GB increments) |
| 4 | 8 GB - 30 GB (1 GB increments) |
| 8 | 16 GB - 60 GB (4 GB increments) |
| 16 | 32 GB - 120 GB (8 GB increments) |
Sizing Guidelines
# Sizing decision tree
def recommend_fargate_size(app_type: str, expected_memory_mb: int):
"""Recommend Fargate CPU/Memory based on workload"""
if app_type == "web_api":
# Typically CPU-bound
if expected_memory_mb <= 512:
return {"cpu": "256", "memory": "512"}
elif expected_memory_mb <= 1024:
return {"cpu": "512", "memory": "1024"}
else:
return {"cpu": "1024", "memory": str(min(expected_memory_mb, 2048))}
elif app_type == "worker":
# Typically memory-bound
return {"cpu": "256", "memory": str(max(512, expected_memory_mb))}
elif app_type == "batch":
# Need burst capacity
return {"cpu": "1024", "memory": str(max(2048, expected_memory_mb))}
# Default conservative
return {"cpu": "512", "memory": "1024"}
Cost-Effective Sizing
# Development: Minimum viable
cpu = "256"
memory = "512"
# Small API: Balanced
cpu = "512"
memory = "1024"
# Production API: Standard
cpu = "1024"
memory = "2048"
# Memory-intensive: Data processing
cpu = "1024"
memory = "4096"
# Compute-intensive: ML inference
cpu = "4096"
memory = "8192"
Platform Versions
Current Versions (2025)
| Version | Status | Features |
|---|---|---|
| 1.4.0 | Recommended | containerd runtime, jumbo frames, UDP support |
| 1.3.0 | Deprecated (Mar 2026) | Docker runtime |
| LATEST | Auto-updates | Always latest revision |
Configuration
# Always pin explicitly
resource "aws_ecs_service" "app" {
platform_version = "1.4.0" # NOT "LATEST"
# ...
}
# Boto3
ecs.create_service(
platformVersion='1.4.0',
# ...
)
Fargate Spot (Cost Optimization)
Overview
- Up to 70% savings vs on-demand
- 2-minute termination notice when capacity needed
- Best for: Batch jobs, CI/CD, fault-tolerant workloads
Configuration
# Mixed strategy: reliable baseline + cost-effective scaling
resource "aws_ecs_service" "app" {
capacity_provider_strategy {
capacity_provider = "FARGATE"
weight = 1
base = 2 # Always keep 2 on-demand
}
capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
weight = 4 # 4x weight = ~80% on Spot
}
}
# Boto3
ecs.create_service(
capacityProviderStrategy=[
{'capacityProvider': 'FARGATE', 'weight': 1, 'base': 2},
{'capacityProvider': 'FARGATE_SPOT', 'weight': 4}
],
# ...
)
Handling Spot Interruptions
# Application-level graceful shutdown
import signal
import sys
def shutdown_handler(signum, frame):
print("Received termination signal, shutting down gracefully...")
# Drain connections
# Complete in-flight requests
# Save state if needed
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown_handler)
ARM/Graviton (20% Cost Savings)
Benefits
- ~20% cost reduction vs x86
- Up to 40% better performance for many workloads
- Same API - just change task definition
Configuration
resource "aws_ecs_task_definition" "app" {
family = "my-app"
requires_compatibilities = ["FARGATE"]
# ARM architecture
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "ARM64" # or "X86_64"
}
# ...
}
Multi-Architecture Images
# Dockerfile supporting both architectures
FROM python:3.11-slim
# Build for multiple platforms
# docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
# Build and push multi-arch image
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
-t 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest .
Networking (awsvpc Mode)
Task-Level Networking
Each Fargate task gets its own ENI with:
- Unique private IP
- Dedicated security group
- Full network isolation
resource "aws_ecs_service" "app" {
network_configuration {
subnets = var.private_subnets
security_groups = [aws_security_group.ecs_tasks.id]
assign_public_ip = false # Use NAT for outbound
}
}
VPC Endpoints (Recommended)
# Avoid NAT costs for AWS services
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = module.vpc.vpc_id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = module.vpc.private_subnets
}
resource "aws_vpc_endpoint" "ecr_dkr" {
vpc_id = module.vpc.vpc_id
service_name = "com.amazonaws.${var.region}.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = module.vpc.private_subnets
}
resource "aws_vpc_endpoint" "s3" {
vpc_id = module.vpc.vpc_id
service_name = "com.amazonaws.${var.region}.s3"
}
resource "aws_vpc_endpoint" "logs" {
vpc_id = module.vpc.vpc_id
service_name = "com.amazonaws.${var.region}.logs"
vpc_endpoint_type = "Interface"
subnet_ids = module.vpc.private_subnets
}
EFS Integration (Persistent Storage)
Configuration
# EFS File System
resource "aws_efs_file_system" "app" {
creation_token = "app-storage"
encrypted = true
lifecycle_policy {
transition_to_ia = "AFTER_30_DAYS"
}
}
resource "aws_efs_mount_target" "app" {
count = length(var.private_subnets)
file_system_id = aws_efs_file_system.app.id
subnet_id = var.private_subnets[count.index]
security_groups = [aws_security_group.efs.id]
}
# Task Definition with EFS
resource "aws_ecs_task_definition" "app" {
family = "my-app"
volume {
name = "app-storage"
efs_volume_configuration {
file_system_id = aws_efs_file_system.app.id
root_directory = "/app-data"
transit_encryption = "ENABLED"
authorization_config {
access_point_id = aws_efs_access_point.app.id
iam = "ENABLED"
}
}
}
container_definitions = jsonencode([
{
name = "my-app"
mountPoints = [
{
sourceVolume = "app-storage"
containerPath = "/data"
readOnly = false
}
]
# ...
}
])
}
Ephemeral Storage
Default and Configurable
# Default: 20 GB, configurable up to 200 GB
resource "aws_ecs_task_definition" "app" {
ephemeral_storage {
size_in_gib = 100 # For large temp files
}
}
Cost Optimization Summary
| Strategy | Savings | Effort |
|---|---|---|
| Fargate Spot | 70% | Low |
| ARM/Graviton | 20% | Medium |
| Right-sizing | 10-50% | Medium |
| Compute Savings Plans | 50% | Low |
| Schedule-based scaling | 30-60% | Medium |
Cost Calculation
def estimate_monthly_cost(cpu_vcpu: float, memory_gb: float,
hours_per_month: int = 730,
spot_percentage: float = 0.0):
"""Estimate monthly Fargate cost (us-east-1 pricing)"""
# On-demand pricing (approximate)
cpu_rate = 0.04048 # per vCPU-hour
memory_rate = 0.004445 # per GB-hour
on_demand_hours = hours_per_month * (1 - spot_percentage)
spot_hours = hours_per_month * spot_percentage
# Spot is ~70% cheaper
cpu_cost = (cpu_vcpu * cpu_rate * on_demand_hours) + \
(cpu_vcpu * cpu_rate * 0.3 * spot_hours)
memory_cost = (memory_gb * memory_rate * on_demand_hours) + \
(memory_gb * memory_rate * 0.3 * spot_hours)
return cpu_cost + memory_cost
# Example: 1 vCPU, 2 GB, 50% Spot
cost = estimate_monthly_cost(1, 2, spot_percentage=0.5)
print(f"Estimated monthly cost: ${cost:.2f}") # ~$25
Progressive Disclosure
Quick Start (This File)
- Fargate vs EC2 decision
- CPU/Memory sizing
- Platform versions
- Cost optimization basics
Detailed References
- Sizing Guide: Detailed sizing strategies
- Networking: VPC, security groups, endpoints
- Storage: EFS, ephemeral storage patterns
Related Skills
- boto3-ecs: SDK patterns for ECS
- terraform-ecs: Infrastructure as Code
- ecs-deployment: Deployment strategies
- ecs-troubleshooting: Debugging guide
Best Practices
- Start small, scale up - Begin with 0.25 vCPU/512 MB and increase based on metrics
- Use Fargate Spot for non-critical workloads (70% savings)
- Consider ARM/Graviton for compatible workloads (20% savings)
- Pin platform version explicitly (e.g., "1.4.0")
- Use VPC endpoints to reduce NAT costs
- Enable Container Insights for visibility
- Right-size continuously using CloudWatch metrics
- Use capacity provider strategy instead of launch type