High reserved concurrency isn't the red flag: it's free. Concurrency that stays high all day is. An API that waits on Postgres costs several times more on Lambda than in a container once it's busy around the clock; spiky traffic, events and CPU-heavy work stay on Lambda. Usually both: a container for the steady core, Lambda for the rest.
When to pick which
Pick Lambda when
- Traffic is spiky: the daily average is a tenth of the peak or less. Lambda scales by 1,000 environments every 10 seconds and costs nothing in between.
- It reacts to events: queues, schedules, S3, EventBridge.
- The code is CPU-bound: a container only wins once it's busy more than about half the time.
- Moving would save less than about $300 a month: not worth a load balancer, health checks and deploys to own.
Pick Fargate when
- One HTTP function's concurrency stays at about 10 or more all night, or averages 30% of its daily peak, and its Lambda bill passes about $500 a month.
- It mostly waits on Postgres or other APIs: one container handles many requests at once, where Lambda bills each one for its whole wait.
- A hard limit hits: requests over 15 minutes, WebSockets, more than 10 GB of memory, or Postgres running out of connections.
Pick EC2 when
- You need what Fargate lacks: GPUs, special instance types, local disks.
- A three-year commitment on a large, steady fleet, where the last few percent matter more than patching.
Both, when
- The usual answer: the steady HTTP core in a container, and Lambda for events, queues, schedules and spikes, on the same code where possible.
- Lambda Managed Instances is the middle ground: a steady function that should stay a Lambda runs on EC2 capacity you choose, at EC2 prices plus 15%, from about 30 sustained executions.
Questions that change the answer
What is the function's concurrency at 3 am? CloudWatch's ConcurrentExecutions, as an average, a floor and a peak: that floor is the steady load a container would carry.
How much of each request is CPU, and how much is waiting? Mostly waiting favours a container; mostly CPU favours Lambda.
What does that one function cost a month? Divide the bill by about $38 (1 GB on Arm) to get its average concurrency.
Who will own the load balancer, the scaling and the deploys? That's the real cost of moving.
The short version
Reserved concurrency costs nothing: it's a cap and a guarantee. A function with 300 reserved can be the right design, if it sits at 2 most of the day and jumps to 300 for ten minutes. The signal is in CloudWatch's ConcurrentExecutions: how high it stays overnight, and how close the average is to the peak.
| Your function | Stay on Lambda | Move to a container |
|---|---|---|
| Traffic | Spiky: average a tenth of the peak or less | Steady: about 10+ concurrent all night |
| Work | CPU-bound, or events and queues | HTTP that mostly waits on Postgres or APIs |
| Bill for that function | Under about $300 a month saved by moving | About $500 a month or more |
| Limits | Within 15 minutes, 10 GB, no WebSockets | Over 15 minutes, WebSockets, GPUs, connection storms |
Why waiting is the whole question
One Lambda environment serves one request at a time and bills for every millisecond it's open, waiting included. A container on one vCPU serves many requests at once while they wait. So the answer depends on how much of each request is real CPU work:
| Workload | CPU share of a request | Requests one vCPU carries at once | Container cheaper by |
|---|---|---|---|
| Thin API over Postgres, auth, proxy | ~5% | ~12 | ~12× |
| Typical JSON API, a few queries | ~10% | ~6 | ~6× |
| API with real business logic | ~25% | ~2.4 | ~2.4× |
| Server rendering, PDFs, images | ~50% | ~1.2 | about even |
| CPU-bound | 60%+ | 1 | none |
Sydney, Arm, 1 GB Lambda at 200 ms per request against a 1 vCPU / 2 GB Fargate task; the shares are typical, measure your own.
Three functions, priced (Sydney, per month)
| Function | Lambda | Container | Call |
|---|---|---|---|
| An API averaging 5 concurrent all day | $188 | Fargate, two small tasks and a load balancer: $73 | Stay. $115 a month doesn't pay for owning the container. |
| An API averaging 50 concurrent all day | $1,883 | Fargate: $437 (or $896 if a quarter of each request is CPU). EC2: $427. Lambda Managed Instances: $560 | Move. That's $1,000 to $1,450 a month on one function. |
| 2 concurrent, then 300 for ten minutes a day | $153 | Fargate sized for the peak: about $1,960 | Stay. This is what Lambda is for. |
Assumptions: Arm, 1 GB, 200 ms per request, an Application Load Balancer and public IPv4 addresses included for Fargate and EC2, one request in ten spent on CPU unless stated. The smallest highly available Fargate setup (two tasks in two zones, a load balancer, four public addresses) costs about $73 a month before it serves a request: that's the floor a move has to beat.
To read it off the bill: one environment busy all month costs about $38 on Arm at 1 GB. A function costing $1,900 a month is running about 50 at once, on average.
What doesn't change the answer
- Provisioned concurrency never makes Lambda cheaper in Sydney, even fully used. It buys latency, not price.
- Savings Plans widen the gap: they take 12 to 17% off Lambda's duration and nothing off its requests, against 21 to 46% off Fargate on Arm.
- EC2 against Fargate: in Sydney, on demand, the same shape costs about the same (a c7g.large is $0.0944 an hour, Fargate's equivalent $0.0947). Pick EC2 for what Fargate can't do, not for price.
How I'd move one function
- Measure first. A week of
ConcurrentExecutions(average, overnight floor, peak) and the share of time each request spends waiting. - Move only the steady HTTP core. One Node.js service on Fargate, Arm, behind a load balancer. ECS Express Mode now sets up the service, the load balancer, health checks and scaling for you, at no extra charge. App Runner is closed to new customers.
- Keep Lambda for the rest. Events, queues, schedules and the spikes stay where they are.
- Keep the address. With the domain on a Worker or CloudFront, the move is an origin change (Function URL or API Gateway?).
- Commit later. A Compute Savings Plan covers Fargate once three months of usage show the floor.
The middle ground: Lambda Managed Instances
In Sydney since June 2026, Lambda Managed Instances run your function on EC2 instances you choose, with many requests per environment (64 per vCPU for Node.js by default), billed at the EC2 price plus a 15% fee plus Lambda's request charge. It keeps Lambda's programming and event model, and Savings Plans apply to the EC2 part.
The catches: three instances by default and no scale to zero, functions of at least 2 GB, and throttling when traffic more than doubles within five minutes. So it suits a steady function, about 30 concurrent and up, that you want to keep as a Lambda. Not a spiky one.
Lambda Web Functions, once in Sydney
Lambda Web Functions fix the same problem from the other side: many requests per environment, billed on CPU used, without leaving Lambda. They launched on 12 October 2026 in five regions, not Sydney. When they arrive, test them first, before a container (Lambda, Web Functions or Workers?).
Sources, checked on 25 October 2026: Lambda pricing, Fargate pricing, Lambda concurrency, Lambda scaling, Lambda quotas, Lambda Managed Instances, ECS Express Mode, Elastic Load Balancing pricing.