Serverless is not cheap or expensive. It is a different unit of purchase — compute by the millisecond instead of by the hour — and whether that saves money depends entirely on how much of the hour you were using.
The argument usually happens twice. Once at the start, when serverless is obviously right because there is no traffic and no operations team. Then again eighteen months later, when the bill has grown faster than usage and somebody suggests containers.
Both conversations are usually held with anecdotes rather than arithmetic, which is a shame, because the arithmetic is simple and it settles the question in an afternoon.
All prices below are AWS us-east-1 list prices at the time of writing and ignore free tiers; your region and negotiated rates will differ. What carries over is the method and the break-even, not the exact dollars.
Both models, reduced to one comparable number
Lambda charges per request plus per gigabyte-second of memory held while running. Fargate charges per vCPU-hour and per GB-hour for as long as the task exists, busy or not. To compare them you need one unit, and the useful one is the cost of a continuously-busy vCPU for an hour.
Lambda allocates CPU in proportion to memory: at 1,769MB you get approximately one full vCPU. So one vCPU-hour of Lambda is 1.769GB × 3,600s = 6,368 GB-seconds, which at $0.0000166667 per GB-second is about $0.1061.
Fargate at one vCPU with 2GB of memory costs $0.04048 + (2 × $0.004445) = about $0.0494 per hour. Lambda compute is therefore roughly 2.1× the price of Fargate compute for the same work — and the entire serverless value proposition is that you only buy the seconds you use.
Utilisation, not request count, is the variable. A million requests a day tells you nothing about which platform is cheaper — memory, duration and how peaky the traffic is tell you everything.
LAMBDA (us-east-1, x86, list)
requests $0.20 per 1,000,000
compute $0.0000166667 per GB-second
1 vCPU-hour equivalent = 1.769 GB x 3600 s = 6,368 GB-s = $0.1061
FARGATE (us-east-1, list)
vCPU $0.04048 per vCPU-hour
memory $0.004445 per GB-hour
1 vCPU + 2 GB, for an hour = $0.0494
BREAK-EVEN
Fargate is cheaper once sustained utilisation exceeds
$0.0494 / $0.1061 = 46.6%
Below ~45% utilisation, you are better off paying the premium
and not buying the idle time. Above it, you are renting the same
compute at twice the price.Three workloads, identical traffic, opposite answers
Take thirty million requests a month — about eleven and a half per second on average — and change only the shape of the work. The conclusion flips twice.
Short and light favours serverless, because the minimum viable container footprint is larger than the work. You cannot run half a task, and you want at least two for availability, so a container platform starts at a floor you pay regardless.
Long and heavy favours containers, because you are buying a lot of compute and Lambda charges a premium per unit. Very spiky favours serverless hardest of all, because containers must be provisioned for the peak while Lambda is billed for the average.
- Duration and memory multiply: Lambda cost is requests × duration × memory. Doubling memory to halve duration is cost-neutral on compute and usually a latency win — worth testing, since the default 128MB is slower and rarely cheaper overall.
- Containers have a floor: Two small tasks plus a load balancer is roughly $90 a month before serving anything. Below that level of work, serverless wins on arithmetic alone.
- Peak-to-average ratio is the hidden variable: Containers are sized for the peak and billed for the hour. The spikier the traffic, the more of what you buy is idle, and the better serverless looks.
| Workload | Lambda | Fargate | Cheaper |
|---|---|---|---|
| 120ms, 512MB — a typical CRUD API | $66 ($30 compute, $6 requests, $30 gateway) | $90 (2 × 1 vCPU tasks, ~20% utilised, + ALB) | Lambda, by a little |
| 800ms, 1GB — reports, PDF, image work | $436 ($400 compute, $6 requests, $30 gateway) | $284 (~3,800 vCPU-hours of work at ~70% utilisation, + ALB) | Fargate, by ~35% |
| 200ms, 512MB, peaks 50× average | $9 | $150+ (provisioned for peak, partially autoscaled) | Lambda, overwhelmingly |
The charges the calculators leave out
Comparing compute alone is how migrations end up costing more than they saved. Four adjacent charges regularly exceed the compute difference.
The API Gateway choice is the largest and the easiest to get wrong: REST APIs cost $3.50 per million requests, HTTP APIs $1.00. On thirty million requests a month that is $105 against $30, for a difference most teams never notice they chose. If you do not need REST-only features, use the HTTP API.
A Lambda attached to a VPC — which it must be to reach a private database — sends its outbound traffic through your NAT gateway, and you pay per gigabyte. Provisioned concurrency, added to fix cold starts, is billed by the hour whether requests arrive or not, which quietly reinstates the idle cost serverless was supposed to remove. And CloudWatch Logs ingestion at $0.50 per gigabyte punishes chatty function logging far more than it punishes a container writing to the same place.
| Charge | Serverless | Containers |
|---|---|---|
| API front door | API Gateway: $1.00/M (HTTP) or $3.50/M (REST) | ALB: ~$16–25/month plus modest LCU charges |
| Reaching a private database | VPC-attached Lambda pays NAT data processing | Usually inside the VPC already, no NAT charge |
| Removing cold starts | Provisioned concurrency, billed hourly — idle cost returns | Not applicable; the task is already warm |
| Log ingestion | $0.50/GB, and per-invocation logging adds up fast | Same rate, but usually far fewer log streams |
| Commitments | Compute Savings Plans cover Lambda | Compute Savings Plans cover Fargate; Fargate Spot cuts more |
| Database connections | Needs RDS Proxy or a pooler — one per instance is a ceiling problem | A normal shared pool per task |
Where each one is clearly right
Most of the argument disappears once you stop treating it as a platform decision and start treating it as a per-workload one. Nearly every SaaS I work with should be running both, with each workload on the model that suits its shape.
Serverless earns its premium for anything spiky, infrequent or event-shaped: webhooks, scheduled jobs, image processing on upload, admin tooling, the first version of any feature whose traffic you cannot predict. The operational savings are real and they are worth more than the compute premium at low volume.
Containers earn their keep for steady, sustained, latency-sensitive work: the main API once traffic is continuous, anything holding long-lived connections, workloads needing more than fifteen minutes, and anything where you want a normal database connection pool rather than a proxy.
| Workload | Better fit | Why |
|---|---|---|
| Webhook receivers | Serverless | Spiky, short, and idle most of the day. |
| Scheduled and batch jobs | Serverless | You pay for the minutes they run, not the hours between. |
| The main request-serving API | Containers, once traffic is steady | Sustained utilisation is where the 2.1× premium starts to hurt. |
| Long-running AI or report jobs | Containers or a queue worker | Duration × memory is exactly what Lambda charges most for — and there is a 15-minute ceiling. |
| WebSockets and streaming | Containers | Long-lived connections fight the serverless billing model. |
| A feature with unknown traffic | Serverless | Buy optionality while you learn the shape; move it later if it settles. |
Model your own numbers before you migrate anything
You need four measurements, all of which you already have: requests per month, p50 duration, memory actually used (not allocated), and the ratio of peak to average traffic. Ten minutes in CloudWatch produces all four.
Then run both models. The script below is deliberately small — the point is not precision, it is finding out whether the answer is close or obvious. If the two numbers land within twenty percent of each other, stay where you are: the migration will cost more in engineering time than it returns for years.
Watch for the memory number in particular. Teams routinely allocate 1GB to a function using 180MB, and since Lambda bills allocated memory rather than used, that is a five-fold overcharge on every invocation and the easiest saving available without changing platforms at all.
const REQ_PER_MONTH = 30_000_000
const DURATION_S = 0.12 // p50, measured
const MEMORY_GB = 0.5 // allocated, not used
const PEAK_RATIO = 4 // peak rps / average rps
const GATEWAY = 'http' // 'http' $1.00/M, 'rest' $3.50/M
// --- Lambda -------------------------------------------------------------
const lambdaCompute = REQ_PER_MONTH * DURATION_S * MEMORY_GB * 0.0000166667
const lambdaRequests = (REQ_PER_MONTH / 1e6) * 0.20
const gateway = (REQ_PER_MONTH / 1e6) * (GATEWAY === 'rest' ? 3.5 : 1.0)
const lambdaTotal = lambdaCompute + lambdaRequests + gateway
// --- Fargate ------------------------------------------------------------
// Work actually done, in vCPU-hours. Lambda gives 1 vCPU at 1.769GB.
const vcpuHours = (REQ_PER_MONTH * DURATION_S * (MEMORY_GB / 1.769)) / 3600
// You must provision for the peak, and never fewer than 2 tasks.
const provisioned = Math.max(2 * 730, vcpuHours * PEAK_RATIO)
const fargate = provisioned * (0.04048 + 2 * 0.004445) + 18 // + ALB
console.log({
lambda: lambdaTotal.toFixed(0),
fargate: fargate.toFixed(0),
utilisation: (vcpuHours / provisioned * 100).toFixed(0) + '%',
})
// Utilisation under ~45% and Lambda is usually the cheaper answer.
// Over it, you are paying the serverless premium on compute you are using.If you do move, move one route at a time
A platform migration done as a single cutover is the most expensive way to find out your model was wrong. Both platforms can sit behind the same front door, so move one endpoint, tag everything, and read the bill after a full billing cycle.
Pick the workload where the model predicts the biggest difference — usually the longest-running, highest-memory endpoint — and move that first. It gives you the clearest signal and the fastest payback, and if the prediction turns out wrong, you have changed one route rather than an architecture.
Tag by workload before you start, not after. Without tags you will be comparing two months of a mixed bill and arguing about which part moved, which is exactly the argument the migration was supposed to end.
- Keep one front door: An ALB or API Gateway can route some paths to Lambda and others to a container service. The client never knows a migration is happening.
- Measure over a full billing cycle: Weekly patterns and month-end batch work distort anything shorter. One route, one month, then decide.
- Right-size memory before you conclude anything: Over-allocated Lambda memory makes serverless look worse than it is. Fix that first, then re-run the model — sometimes the migration disappears.
Frequently asked questions
Is serverless cheaper than containers?
Below roughly 45% sustained utilisation, usually yes. Above it, usually no. Lambda compute costs about 2.1× Fargate per vCPU-hour ($0.1061 against $0.0494 at us-east-1 list prices), and the whole serverless proposition is that you only buy the seconds you use. Spiky or infrequent workloads stay well under the break-even; a continuously busy API does not.
At what scale should I move off Lambda?
Scale is the wrong trigger — shape is. Compute the vCPU-hours of work your functions actually do each month and divide by what you would have to provision in containers. If that utilisation is consistently above about half, containers are cheaper. A very high-volume but short and bursty API can stay on Lambda profitably for a long time.
Does API Gateway really matter that much?
Often more than the compute difference. REST APIs cost $3.50 per million requests against $1.00 for HTTP APIs — on 30 million requests a month that is $105 versus $30. Many teams are on REST by default because it is the older option, not because they use its features. Check before you model anything else.
What about cold starts — are they a cost issue?
They become one. Provisioned concurrency is the usual fix and it is billed hourly whether requests arrive or not, which reintroduces exactly the idle cost serverless was chosen to avoid. If you need provisioned concurrency across your main API, re-run the comparison — you may already be paying container economics with serverless ergonomics.
Can I use Savings Plans with serverless?
Yes. Compute Savings Plans cover Lambda as well as Fargate and EC2, so a commitment on your steady floor applies either way. Fargate additionally has Spot, which is dramatically cheaper for interruptible work — batch jobs, queue workers and CI — but not appropriate for your request-serving tier.
Should a small SaaS pick one platform for everything?
No, and trying to is how both bills get worse. Run webhooks, scheduled jobs and unpredictable new features on serverless, and the steady request-serving core on containers. The decision belongs to the workload, and both can sit behind the same load balancer so the client never knows.
