Serverless Is Not Always Cheaper: When to Move Off Lambda
The Serverless Cost Myth
Serverless is marketed as the cheaper option because you only pay for what you use. That is true at low volumes. But Lambda pricing is linear: double the invocations, double the cost. Container pricing is step-function: you pay for capacity blocks, and each block handles a range of traffic. At some point the lines cross, and Lambda becomes significantly more expensive. We see this crossover more often than most teams expect, typically between 10 and 30 million invocations per month depending on function memory and duration. The problem is compounded by the fact that teams rarely revisit the Lambda decision once it is made. The architecture calcifies, dependencies on Lambda-specific patterns (event source mappings, Powertools for AWS Lambda, custom layers) accumulate, and by the time someone notices the bill, migration feels daunting.
The Math Nobody Does Upfront
Lambda costs $0.20 per million invocations plus $0.0000166667 per GB-second of compute. A 256MB function running for 500ms costs approximately $0.0000021 per invocation. At 1 million requests per month, that is $2.10, essentially free. At 100 million requests per month, that is $210 in Lambda compute alone. Now compare: a Fargate task with 0.5 vCPU ($0.04048/hour) and 1GB memory ($0.004445/hour) running 24/7 costs about $32.40/month and can handle 200 to 500 requests per second depending on your workload. That translates to roughly 500 million to 1.3 billion requests per month from a single task. The crossover point for this configuration is approximately 15 to 20 million requests per month. Beyond that, you are overpaying for serverless. We build a simple cost model for clients using a spreadsheet that plots Lambda cost vs Fargate cost across their projected traffic range, making the crossover point visually obvious.
Hidden Costs That Add Up
Lambda's per-invocation cost is only part of the picture. API Gateway (REST API type) adds $3.50 per million requests and $0.09/GB for data transfer. If you are using HTTP API instead of REST API, it is $1.00 per million, which helps but still adds up. CloudWatch Logs charges $0.50 per GB ingested for every log line Lambda emits, and Lambda functions are notoriously chatty loggers by default. If you are using Provisioned Concurrency to avoid cold starts, you pay $0.0000041667 per GB-second of provisioned capacity, which is essentially paying for reserved compute. That negates the 'pay only for what you use' benefit. We audited one client who was spending $4,200/month on Lambda plus $2,800/month on API Gateway and CloudWatch Logs plus $1,100/month on Provisioned Concurrency for a set of 12 APIs that could run on two Fargate tasks behind an ALB for $120/month total. The ALB adds $22/month plus $0.008 per LCU-hour, which is still a fraction of the API Gateway cost.
Signs You Should Move Off Lambda
You should evaluate a migration from Lambda to containers when: your functions consistently exceed 10 million invocations per month per function, you are using Provisioned Concurrency on more than 5 functions, your P95 latency requirements are under 100ms (cold starts make this unreliable even with SnapStart or Provisioned Concurrency), you need persistent database connections (Lambda's connection pooling with RDS Proxy adds $0.015 per vCPU-hour and introduces its own latency), your functions share significant code and would be simpler as a single Express.js or FastAPI service with multiple endpoints, or you are hitting Lambda concurrency limits (default 1,000 per region) and requesting quota increases regularly. We also recommend moving off Lambda when your team is spending significant time debugging Lambda-specific issues: cold starts, timeout errors at the 15-minute limit, package size constraints (250MB unzipped, 50MB zipped for direct upload), or ephemeral storage limitations.
How We Handle the Migration
We do not recommend ripping out Lambda entirely. The move is surgical: identify the high-volume, latency-sensitive functions that are costing the most, containerize those as ECS Fargate services, and keep Lambda for the event-driven, bursty workloads where it still wins. Our migration process follows a clear sequence. First, we instrument every Lambda function with X-Ray tracing and export metrics to a cost dashboard, tagging each function with its monthly invocation count, average duration, and total cost. Second, we rank functions by monthly cost and identify the top 20% that account for 80% of spend. Third, we containerize those functions into grouped services using a framework like Express.js (Node.js) or FastAPI (Python), keeping the same API contracts so downstream consumers are unaffected. Fourth, we deploy to ECS Fargate behind an ALB, validate with shadow traffic for 1 week, then switch over. On a recent engagement, we migrated 8 high-traffic Lambda functions into 3 Fargate services. Monthly compute cost dropped from $3,800 to $890. P95 latency improved by 60% (from 340ms to 135ms) because cold starts were eliminated entirely. The team also stopped dealing with cold start complaints from users hitting the login and search endpoints.