Two "Serverless" Services, Two Radically Different Models
AWS Lambda and ECS Fargate are both called "serverless" — you do not manage the underlying servers. But their execution models are fundamentally different, and confusing the two leads to suboptimal or costly architectures. This guide gives you the framework to choose correctly.
AWS Lambda: Function-as-a-Service
Lambda executes your code in response to events. Each invocation is independent and ephemeral. Key characteristics:
- Maximum duration: 15 minutes per invocation (hard limit)
- Memory: 128 MB to 10 GB (CPU allocated proportionally to memory)
- Ephemeral storage: /tmp up to 10 GB (512 MB default, paid increase)
- Cold starts: 50 ms to 2 s depending on runtime — Python/Node fast, Java slow without SnapStart
- Native triggers: S3, SQS, SNS, DynamoDB Streams, API Gateway, EventBridge, Kinesis
- Concurrency: 1,000 simultaneous executions by default (increasable to 10,000+)
ECS Fargate: Container-as-a-Service
Fargate runs Docker containers without you provisioning EC2 instances. The process starts once and handles multiple requests:
- No time limit: long-running processes (ML inference, WebSockets, batch jobs) work natively
- Resources: 0.25 to 16 vCPU, 512 MB to 120 GB RAM per task
- Native networking: ALB/NLB integration, full VPC, Security Groups, persistent database connections
- Any runtime: any language, any system library — complete freedom via Docker
- Startup time: 30–60 seconds to start a new task (versus milliseconds for Lambda)
Comparison Table — 10 Criteria
| Criterion | Lambda | ECS Fargate |
|---|---|---|
| Execution duration | Max 15 min | Unlimited |
| Cold start | 50ms–2s | 30–60s (task startup) |
| Max memory | 10 GB | 120 GB |
| Networking | VPC optional | Native VPC |
| Local development | SAM CLI (complex) | docker-compose (simple) |
| Cost model | Per invocation | Per hour |
| Scale-out speed | Immediate (ms) | 30–90s (new task) |
| Persistent connections | No (RDS Proxy needed) | Yes (native) |
| Custom runtime | Limited (layers) | Full (Docker) |
| Vendor lock-in | High | Moderate (Docker standard) |
Use Lambda When
- Event-driven processing: S3 uploads, SQS messages, DynamoDB Streams, EventBridge rules — Lambda was designed for this
- Variable or bursty traffic: a webhook receiving zero requests at night and 500/second during a marketing spike — Lambda scales from 0 to 500 instantly
- Execution under 15 minutes: validation, transformation, notification, image resizing
- Heavy use of native AWS integrations: if your application deeply consumes S3, DynamoDB, SQS, Kinesis — Lambda integrates natively with minimal configuration
- Low to medium volume: under ~3M invocations/month at 512 MB — Lambda will be cheaper than any Fargate configuration
Use Fargate When
- Long-running processes: batch jobs exceeding 15 minutes, ML model serving, WebSocket servers
- WebSocket or gRPC: long-lived connections incompatible with Lambda's 15-minute timeout
- Memory over 10 GB: Lambda hard limit is 10 GB; Fargate goes up to 120 GB per task
- Predictable sustained traffic: if your API receives steady traffic 24/7, Fargate's hourly billing becomes more cost-effective than per-invocation billing
- Existing Docker images: if you already have a containerised application, Fargate avoids rewriting code as Lambda functions
Cost Breakeven Analysis
For a typical workload (512 MB RAM, 500 ms average duration), the breakeven point where Fargate becomes cheaper than Lambda is around 3 million invocations per month for sustained traffic:
# Lambda (512 MB, 500ms, 3M invocations/month)
Compute : 3M × 0.5s × 0.0000000083 $/GB-s × 0.5 = $0.62
Requests : 3M × 0.0000002 $ = $0.60
Lambda total: ~$1.22/month
# Fargate (0.25 vCPU, 0.5 GB, 1 task running 24/7)
vCPU : 0.25 × $0.04048/h × 730h = $7.39
Memory : 0.5 × $0.004445/h × 730h = $1.62
Fargate total: ~$9.01/month
→ Lambda is 7x cheaper for 3M req/month sporadic traffic
# At sustained continuous traffic (10,000 req/s):
# Lambda: 10K × 3600 × 24 × 30 × 0.5s × 0.0000000083 × 0.5 = ~$540/month
# Fargate (10 tasks × 1 vCPU): ~$295/month with Savings Plans
# → Fargate wins at high sustained throughput
Lambda with Container Images: Best of Both Worlds
Since 2020, Lambda supports ECR container images up to 10 GB. This allows deploying complex system dependencies (ffmpeg, pandoc, ML libraries) while keeping Lambda's per-invocation billing model:
# Dockerfile for Lambda container image
FROM public.ecr.aws/lambda/python:3.12
# Install system dependencies (impossible with standard Lambda ZIP)
RUN dnf install -y ffmpeg && dnf clean all
COPY requirements.txt .
RUN pip install -r requirements.txt --no-cache-dir
COPY src/ ${LAMBDA_TASK_ROOT}/
CMD ["handler.lambda_handler"]
Practical Architecture: Lambda + Fargate Together
# Typical SaaS application architecture
# Synchronous layer — Fargate
API Gateway → ECS Fargate (REST API, 200 req/s, ALB)
└── RDS PostgreSQL (persistent connection pool)
# Asynchronous layer — Lambda
S3 Events → Lambda (file processing, image resizing)
SQS Queue → Lambda (order processing, notifications)
EventBridge → Lambda (cron orchestration, workflows)
# Batch layer — Fargate Scheduled Tasks
ECS Fargate → nightly batch jobs (2h+ duration)
ECS Fargate → WebSocket service (real-time, long connections)
Terraform: Lambda + ECS Side by Side
# Lambda function
resource "aws_lambda_function" "order_processor" {
function_name = "order-processor"
runtime = "python3.12"
handler = "handler.lambda_handler"
filename = "function.zip"
timeout = 300
memory_size = 512
role = aws_iam_role.lambda_exec.arn
}
# ECS Task Definition for the main API
resource "aws_ecs_task_definition" "api" {
family = "api-production"
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 = "api"
image = "${aws_ecr_repository.api.repository_url}:latest"
essential = true
portMappings = [{ containerPort = 8080, protocol = "tcp" }]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/api-production"
"awslogs-region" = "eu-west-1"
"awslogs-stream-prefix" = "api"
}
}
}])
}
Conclusion
There is no "better" service between Lambda and ECS Fargate — there is the right service for each context. Lambda excels at event-driven, short-lived workloads at low to moderate scale. Fargate excels at long-running, high-throughput, latency-sensitive workloads requiring persistent connections. Most modern production architectures use both: Lambda handles events and webhooks while Fargate runs synchronous APIs and batch processes. Let execution characteristics — duration, traffic pattern, memory requirements, and connection model — drive the decision, not convention.
