Lambda is my default, and a Fargate service is where a Lambda goes when it has stopped being spiky. The signal isn't a high reserved concurrency, which costs nothing. It's concurrency that stays high all day: an API that waits on Postgres pays Lambda for every millisecond of that wait, request by request, while one container serves many of them at once.
The numbers are in Lambda, Fargate or EC2?: at 50 requests in flight all day, about $1,883 a month on Lambda against about $437 on Fargate. At 5, the saving doesn't pay for the container. So I move only the steady HTTP core, measured first, and keep events, queues, schedules and spikes on Lambda.
How I'd wire it: ECS Express Mode on Fargate, Arm, one Node.js service, the domain on a Worker or CloudFront in front so the move is an origin change, and a Compute Savings Plan once three months show the floor.
Use it 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.
- The service mostly waits on Postgres or other APIs: one Node.js process serves many requests at once, where Lambda bills each one for its whole wait.
- A hard Lambda limit hits: requests over 15 minutes, long-lived connections, more than 10 GB of memory, or Postgres running out of connections.
Don't use it when
- Traffic is spiky, with a daily average a tenth of its peak or less: Lambda scales in seconds and costs nothing in between. Sized for a ten-minute peak, a container runs it all day.
- Events, queues and schedules: they stay on Lambda (or a Fargate scheduled task for long jobs).
- The saving would be under about $300 a month: it doesn't pay for owning a load balancer, scaling and deploys.
Pricing
Per second of vCPU and memory, around the clock. Sydney, Arm: $0.03885 per vCPU-hour and $0.00426 per GB-hour (x86: $0.04856 and $0.00532). A 1 vCPU / 2 GB Arm task is about $34.58 a month. Add the Application Load Balancer ($18.40 a month plus about $5.84 per capacity unit) and $3.65 a month per public IPv4 address: the smallest highly available setup, two small tasks in two zones, is about $73 a month. Compute Savings Plans take 21% (1 year) to 46% (3 years) off Fargate on Arm.
Gotchas, with fixes
Gotcha: Target-tracking scaling reacts on one-minute metrics with a cooldown, then new tasks must start: a sudden ten-minute peak is mostly over before the capacity arrives.
Fix: Keep the spiky part on Lambda, or use scheduled scaling when the peak's time is known.
Gotcha: Tasks in private subnets need a NAT gateway to reach ECR, Neon or any API: about $43 a month before data, for every zone.
Fix: Run tasks in public subnets with public IPs and a security group that only accepts the load balancer, or add VPC endpoints when they must stay private.
Gotcha: App Runner, the easy way to run a container service, is closed to new customers.
Fix: Use ECS Express Mode: one call creates the Fargate service, the load balancer with TLS, health checks and scaling, at no extra charge.
Provisioning
Official Pulumi package · Official Terraform provider
aws.ecs.Service, TaskDefinition and an Application Load Balancer in @pulumi/aws (awsx.ecs.FargateService wraps them). ECS Express Mode creates all of it in one call; its Terraform support is official.
Instead, consider
- AWS Lambda Spiky traffic, events, queues, schedules and CPU-heavy work.
- Lambda Managed Instances A steady function you want to keep as a Lambda.
- EC2 GPUs, special instance types or local disks.
- Fargate scheduled tasks A long job that runs on a schedule, not a service that answers all day.
Worked examples
Sources
Last reviewed by Nicolas Dao