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 --platform=$TARGETPLATFORM 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

Related Skills

  • boto3-ecs: SDK patterns for ECS
  • terraform-ecs: Infrastructure as Code
  • ecs-deployment: Deployment strategies
  • ecs-troubleshooting: Debugging guide

Best Practices

  1. Start small, scale up - Begin with 0.25 vCPU/512 MB and increase based on metrics
  2. Use Fargate Spot for non-critical workloads (70% savings)
  3. Consider ARM/Graviton for compatible workloads (20% savings)
  4. Pin platform version explicitly (e.g., "1.4.0")
  5. Use VPC endpoints to reduce NAT costs
  6. Enable Container Insights for visibility
  7. Right-size continuously using CloudWatch metrics
  8. Use capacity provider strategy instead of launch type
Weekly Installs
1
Installed on
claude-code1