Serverless vs Containers: Cost Break-Even Analysis
Determine when serverless makes financial sense versus containers. Analyze cost models, calculate break-even points, and build a decision framework for your workloads.
“Just use serverless” and “containers are cheaper at scale” are both oversimplifications. The truth depends on your traffic patterns, execution time, and operational costs. This guide provides a framework for making the right choice — with real numbers.
Cost Models Compared
Lambda Pricing
lambda_pricing:
requests: $0.20 per 1M requests
compute:
memory_gb_second: $0.0000166667
# CPU scales with memory
example_128mb_1s:
per_request: $0.0000020 # request cost
per_compute: $0.0000021 # 0.125GB × 1s × $0.0000166667
total: $0.0000041
example_1024mb_200ms:
per_request: $0.0000020
per_compute: $0.0000033 # 1GB × 0.2s × $0.0000166667
total: $0.0000053
free_tier:
requests: 1M per month
compute: 400,000 GB-seconds per month
Fargate Pricing
fargate_pricing:
vcpu_per_hour: $0.04048
memory_gb_per_hour: $0.004445
example_0.5vcpu_1gb:
per_hour: $0.02469 # ($0.04048 × 0.5) + ($0.004445 × 1)
per_month_24x7: $18.02
example_1vcpu_2gb:
per_hour: $0.04937
per_month_24x7: $36.04
spot_discount: "~70% off"
spot_example_1vcpu_2gb:
per_month_24x7: ~$10.81
EKS/EC2 Pricing
eks_pricing:
cluster_fee: $0.10/hour ($73/month)
worker_nodes: EC2 pricing (varies)
example_m6i.large_on_demand:
per_hour: $0.096
per_month: $70.08
example_m6i.large_reserved_1yr:
per_month: ~$49 # 30% discount
example_m6i.large_spot:
per_month: ~$25 # 65% discount
Break-Even Calculator
Python Calculator
from dataclasses import dataclass
from typing import Optional
@dataclass
class LambdaConfig:
memory_mb: int
execution_ms: int
requests_per_month: int
@dataclass
class FargateConfig:
vcpu: float
memory_gb: float
tasks: int
hours_per_month: float = 730 # 24x7
@dataclass
class EKSConfig:
instance_type: str
instance_cost_per_hour: float
node_count: int
hours_per_month: float = 730
cluster_fee: float = 73.0 # $0.10/hour
def calculate_lambda_cost(config: LambdaConfig) -> float:
"""Calculate monthly Lambda cost."""
# Request cost
request_cost = (config.requests_per_month / 1_000_000) * 0.20
# Compute cost
memory_gb = config.memory_mb / 1024
duration_seconds = config.execution_ms / 1000
gb_seconds = memory_gb * duration_seconds * config.requests_per_month
compute_cost = gb_seconds * 0.0000166667
# Subtract free tier (first month only concept, ignore for comparison)
return request_cost + compute_cost
def calculate_fargate_cost(config: FargateConfig, spot: bool = False) -> float:
"""Calculate monthly Fargate cost."""
vcpu_cost = config.vcpu * 0.04048 * config.hours_per_month * config.tasks
memory_cost = config.memory_gb * 0.004445 * config.hours_per_month * config.tasks
total = vcpu_cost + memory_cost
if spot:
total *= 0.3 # 70% discount
return total
def calculate_eks_cost(config: EKSConfig) -> float:
"""Calculate monthly EKS cost."""
node_cost = config.instance_cost_per_hour * config.hours_per_month * config.node_count
return config.cluster_fee + node_cost
def find_break_even(
lambda_config: LambdaConfig,
container_cost: float
) -> int:
"""Find requests/month where Lambda cost equals container cost."""
# Cost per request
memory_gb = lambda_config.memory_mb / 1024
duration_seconds = lambda_config.execution_ms / 1000
cost_per_request = (
0.20 / 1_000_000 + # Request
memory_gb * duration_seconds * 0.0000166667 # Compute
)
# Break even = container_cost / cost_per_request
return int(container_cost / cost_per_request)
# Example analysis
def analyze_workload():
"""Compare costs for a typical API workload."""
print("=" * 60)
print("Cost Comparison: API Workload")
print("=" * 60)
# Lambda: 512MB, 200ms average
lambda_configs = [
(100_000, "100K requests/month"),
(1_000_000, "1M requests/month"),
(10_000_000, "10M requests/month"),
(100_000_000, "100M requests/month"),
]
# Fargate: 0.5 vCPU, 1GB, 2 tasks (HA)
fargate_cost = calculate_fargate_cost(
FargateConfig(vcpu=0.5, memory_gb=1, tasks=2)
)
fargate_spot_cost = calculate_fargate_cost(
FargateConfig(vcpu=0.5, memory_gb=1, tasks=2),
spot=True
)
# EKS: 2x t3.medium nodes
eks_cost = calculate_eks_cost(
EKSConfig(
instance_type="t3.medium",
instance_cost_per_hour=0.0416,
node_count=2
)
)
print(f"\nContainer costs (monthly, 24x7):")
print(f" Fargate (On-Demand): ${fargate_cost:.2f}")
print(f" Fargate (Spot): ${fargate_spot_cost:.2f}")
print(f" EKS (On-Demand): ${eks_cost:.2f}")
print(f"\nLambda costs (512MB, 200ms avg):")
for requests, label in lambda_configs:
lambda_cost = calculate_lambda_cost(
LambdaConfig(memory_mb=512, execution_ms=200, requests_per_month=requests)
)
print(f" {label}: ${lambda_cost:.2f}")
# Find break-even
break_even = find_break_even(
LambdaConfig(memory_mb=512, execution_ms=200, requests_per_month=0),
fargate_cost
)
print(f"\nBreak-even point (Lambda vs Fargate On-Demand):")
print(f" {break_even:,} requests/month")
break_even_spot = find_break_even(
LambdaConfig(memory_mb=512, execution_ms=200, requests_per_month=0),
fargate_spot_cost
)
print(f"\nBreak-even point (Lambda vs Fargate Spot):")
print(f" {break_even_spot:,} requests/month")
if __name__ == "__main__":
analyze_workload()
Output Example
============================================================
Cost Comparison: API Workload
============================================================
Container costs (monthly, 24x7):
Fargate (On-Demand): $36.04
Fargate (Spot): $10.81
EKS (On-Demand): $133.74
Lambda costs (512MB, 200ms avg):
100K requests/month: $0.19
1M requests/month: $1.89
10M requests/month: $18.87
100M requests/month: $188.67
Break-even point (Lambda vs Fargate On-Demand):
19,105,263 requests/month
Break-even point (Lambda vs Fargate Spot):
5,731,578 requests/month
Traffic Pattern Analysis
Steady Traffic
For consistent 24x7 traffic:
- < 5M requests/month: Lambda wins
- 5-20M requests/month: Depends on configuration
- > 20M requests/month: Containers likely win
But consider: Can containers handle your peak?
Bursty Traffic
For traffic with 10x peaks:
- Lambda handles bursts automatically
- Containers need provisioned capacity for peaks
- OR containers need complex autoscaling
Example:
- Average: 100 req/sec
- Peak: 1000 req/sec (2 hours/day)
Lambda cost: Based on actual requests
Container cost: Must provision for peak capacity
Visualization
Monthly Requests vs Cost
Cost ($)
|
300 | ╱ Lambda
| ╱
200 | ╱
| ╱
150 | ╱─────────────────╱─────── Fargate
| ╱
100 | ╱
| ╱
50 | ╱
|___________________________________________
1M 5M 10M 20M 50M 100M Requests
Break-even: ~19M requests/month (Lambda 512MB/200ms vs Fargate 0.5vCPU/1GB)
Hidden Costs
Serverless Hidden Costs
serverless_hidden_costs:
cold_starts:
impact: "100-500ms latency on cold start"
mitigation: "Provisioned concurrency ($)"
provisioned_concurrency:
cost: "$0.000004463 per GB-second"
example_10_warm: "~$30/month for 10 instances warm"
api_gateway:
http_api: "$1.00 per million requests"
rest_api: "$3.50 per million requests"
nat_gateway:
per_gb: "$0.045"
per_hour: "$0.045"
warning: "VPC Lambda + NAT can be expensive"
cloudwatch_logs:
ingestion: "$0.50 per GB"
storage: "$0.03 per GB/month"
Container Hidden Costs
container_hidden_costs:
load_balancer:
alb_per_hour: "$0.0225"
alb_per_month: "$16.43 minimum"
lcu_cost: "$0.008 per LCU-hour"
nat_gateway:
same_as_lambda: "If containers in private subnet"
ecr:
storage: "$0.10 per GB/month"
transfer: "Standard data transfer rates"
monitoring:
container_insights: "$0.30 per container/month"
cluster_management:
eks_fee: "$0.10/hour = $73/month"
ecs_fee: "Free"
operational_overhead:
patching: "OS and runtime updates"
security_scanning: "Container image scanning"
orchestration: "Service mesh, secrets, networking"
Decision Framework
Use Serverless When
serverless_good_fit:
traffic_patterns:
- Unpredictable or highly variable traffic
- Significant idle periods
- < 10M requests/month
requirements:
- Fast time-to-market
- Minimal ops overhead
- < 15 minute execution time
- Event-driven architecture
team:
- Small team
- Limited DevOps capacity
- Focus on features over infrastructure
Use Containers When
containers_good_fit:
traffic_patterns:
- Consistent, predictable traffic
- > 20M requests/month
- Long-running processes
requirements:
- Specific runtime requirements
- Need for GPUs
- Stateful applications
- Complex networking requirements
team:
- Dedicated platform team
- Kubernetes expertise
- Existing container infrastructure
Terraform Decision Module
# decision.tf - Compute deployment based on traffic
variable "monthly_requests" {
type = number
description = "Expected monthly requests"
}
variable "avg_execution_ms" {
type = number
description = "Average execution time in ms"
}
variable "memory_mb" {
type = number
description = "Required memory in MB"
}
locals {
# Calculate Lambda cost
lambda_cost_per_request = (
0.20 / 1000000 + # Request
(var.memory_mb / 1024) * (var.avg_execution_ms / 1000) * 0.0000166667 # Compute
)
lambda_monthly_cost = local.lambda_cost_per_request * var.monthly_requests
# Fargate cost (0.5 vCPU, 1GB, 2 tasks)
fargate_monthly_cost = 2 * 730 * ((0.5 * 0.04048) + (1 * 0.004445))
# Decision
use_lambda = local.lambda_monthly_cost < local.fargate_monthly_cost
deployment_recommendation = local.use_lambda ? "lambda" : "fargate"
}
output "cost_analysis" {
value = {
lambda_monthly = local.lambda_monthly_cost
fargate_monthly = local.fargate_monthly_cost
recommendation = local.deployment_recommendation
savings = abs(local.lambda_monthly_cost - local.fargate_monthly_cost)
}
}
Hybrid Approach
Best of Both Worlds
hybrid_architecture:
lambda:
- API endpoints (< 29 seconds)
- Event processing
- Scheduled tasks
- Infrequent operations
containers:
- Long-running services
- Background workers
- ML inference
- Stateful workloads
shared:
- Same VPC
- Same data stores
- Common monitoring
- Single deployment pipeline
Example Architecture
┌────────────────────────────────────┐
│ API Gateway │
└───────────────┬────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Lambda │ │ Lambda │ │ Lambda │
│ Auth/CRUD │ │ Webhooks │ │ Processing │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ SQS │
└──────┬───────┘
│
▼
┌──────────────────┐
│ Fargate │
│ Heavy Workers │
│ (Spot) │
└──────────────────┘
Key Takeaways
- Calculate, don’t assume — break-even varies by workload
- Include hidden costs — NAT Gateway, ALB, API Gateway add up
- Traffic pattern matters — bursty favors Lambda, steady favors containers
- Consider operational cost — your time has value
- Hybrid is often optimal — use the right tool for each component
“The cheapest architecture is the one that matches your traffic pattern. Everything else is either over-provisioning or under-engineering.”