Lambda vs ECS Fargate vs App Runner: Which AWS Compute to Choose
A detailed comparison of AWS Lambda, ECS Fargate, and App Runner covering cold starts, pricing, scaling, and when to use each for your workload.
“Should I use Lambda or containers?” is the question I get asked most often during technical consultations. And my answer is always: “It depends on your workload profile.” Which is unsatisfying, I know. So let me give you the complete framework I use to decide.
After running 40+ production systems across all three compute options, I have strong opinions about when each shines and when each hurts. This is not a theoretical comparison. These are patterns from real projects with real cost data.
Quick Comparison Table
Before we dive deep, here is the executive summary:
| Feature | Lambda | ECS Fargate | App Runner |
|---|---|---|---|
| Cold start | 100ms-2s | None | None |
| Max request timeout | 15 min | Unlimited | 120s |
| Min billing granularity | 1ms | Per-second | Per-second |
| Auto-scaling speed | Instant | 1-3 min | 30s-2 min |
| Max concurrent | 1000 default | Service limits | 200 default |
| VPC required | Optional | Yes | Optional |
| Container support | Yes | Yes | Yes |
| WebSocket support | Via API GW | Native | No |
| gRPC support | No | Yes | No |
| Minimum cost at zero traffic | $0 | ~$10/mo | $0 with pause |
| Operational complexity | Low | Medium | Very Low |
| Customization level | Medium | High | Low |
This table tells one story. But the real decision happens when you map your specific workload against these constraints.
Lambda Deep Dive
Lambda is AWS’s function-as-a-service offering. You upload code, define a trigger, and AWS handles everything else. No servers, no containers to manage, no patching.
When Lambda Shines
Event-driven workloads. Processing S3 uploads, SQS messages, DynamoDB streams, SNS notifications. Lambda was built for this. You pay nothing when no events arrive.
Bursty, unpredictable traffic. If your API goes from 10 requests/minute to 10,000 requests/minute without warning, Lambda handles it without pre-provisioning.
Scheduled tasks. CRON jobs that run for under 15 minutes. No need to keep a server running 24/7 for a task that executes 3 times a day.
Glue code and integrations. Webhook receivers, data transformations, API proxies. Small functions that connect systems.
Lambda Pricing Model
Lambda charges on two dimensions:
- Requests: $0.20 per 1 million invocations
- Duration: $0.0000166667 per GB-second
Example calculation for a REST API:
50,000 requests/day = 1.5M requests/month
Average duration: 200ms at 512MB memory
Request cost: 1.5M * $0.20/1M = $0.30
Duration cost: 1.5M * 0.2s * 0.5GB * $0.0000166667 = $2.50
Total: ~$2.80/month
At low traffic, Lambda is absurdly cheap. But watch what happens when we scale up:
5,000,000 requests/day = 150M requests/month
Average duration: 200ms at 512MB memory
Request cost: 150M * $0.20/1M = $30
Duration cost: 150M * 0.2s * 0.5GB * $0.0000166667 = $250
Total: ~$280/month
At 5M requests/day, you are paying $280/month. A Fargate service handling the same load would cost roughly $120/month. The crossover point depends on your duration and memory, but it typically happens between 1-5M daily requests.
Lambda Weaknesses
Cold starts are real. First invocation after idle time adds latency:
- Node.js/Python: 100-300ms
- Java/C#: 500ms-2s
- With VPC: Add 200-500ms
Provisioned concurrency solves this but costs money even when idle, defeating much of the serverless value proposition.
15-minute timeout. Any process that takes longer simply cannot run on Lambda. Video processing, large data exports, and complex ML inference often hit this wall.
No persistent connections. WebSockets, gRPC streaming, and long-polling are not possible. API Gateway WebSocket support exists but adds complexity and cost.
Stateless by design. No local file system persistence between invocations. No in-memory caching across requests. Every invocation starts fresh.
ECS Fargate Deep Dive
Fargate is container-as-a-service. You define your container image, specify CPU and memory, and AWS runs it without any EC2 instances to manage. I wrote extensively about why I prefer Fargate over Kubernetes for small teams.
When Fargate Shines
Long-running APIs with consistent traffic. Your core backend API that handles user requests all day. Fargate gives you predictable performance with no cold starts.
WebSocket and gRPC services. Persistent connections work natively. No workarounds needed.
Workloads needing sub-50ms latency. No cold start penalty. Your container is always warm and ready.
Applications with in-memory state. Connection pools, local caches, loaded ML models. These persist across requests.
Batch processing exceeding 15 minutes. No timeout limits. Run as long as you need.
Fargate Pricing Model
Fargate charges per-second for allocated CPU and memory:
- CPU: $0.04048 per vCPU per hour
- Memory: $0.004445 per GB per hour
Example for a typical API service:
1 service: 0.5 vCPU, 1GB memory, running 24/7
CPU: 0.5 * $0.04048 * 730 hours = $14.78/month
Memory: 1 * $0.004445 * 730 hours = $3.24/month
Total per task: ~$18/month
With 2 tasks for availability: ~$36/month
For the same 50K requests/day workload from the Lambda example, a single Fargate task at 0.25 vCPU can handle it easily, costing around $12/month in compute alone. Add ALB at $22/month and you are at $34/month total. Cheaper than Lambda at scale, more expensive at low traffic.
Fargate Weaknesses
You pay for idle time. At 3 AM when traffic drops to zero, your containers are still running and billing. Auto-scaling helps but you always need minimum 1 task running.
Scaling is not instant. New tasks take 1-3 minutes to provision. During traffic spikes, you need pre-provisioned capacity or accept brief degradation.
More operational overhead than Lambda. Health checks, task definitions, service configuration, ALB setup, security groups. Not difficult, but more moving parts.
NAT Gateway costs. Private subnets need NAT Gateway for outbound internet access. That is $32/month base cost plus data processing charges. A sneaky expense that catches people off guard.
App Runner: The Simpler Alternative
App Runner is AWS’s attempt at “just give me a URL for my container.” Push your code or container image, configure basic settings, and get a running service with auto-scaling, TLS, and load balancing built in.
When App Runner Shines
Internal tools and admin dashboards. Services that do not need maximum performance or customization. Your admin panel, internal API, or staging environment.
Teams without AWS expertise. If nobody on the team knows ECS, App Runner gets you running in minutes instead of hours.
Simple HTTP services. Request-response workloads that do not need WebSockets, gRPC, or complex networking.
Prototype and MVP deployments. Get something live fast without investing in infrastructure design.
App Runner Limitations
120-second request timeout. Cannot handle long-running requests or streaming responses.
No WebSocket or gRPC support. HTTP/HTTPS only.
Limited networking control. VPC connectivity is possible but restricted. No fine-grained security group control.
Fewer scaling options. Basic auto-scaling based on concurrent requests or CPU. No custom metrics.
No task placement control. Cannot specify AZ distribution or instance spread.
Higher per-request cost at scale. The simplicity premium means you pay more per compute unit compared to Fargate.
App Runner Pricing
- Provisioned instances: Same rates as Fargate compute
- Pause-to-zero: Services can scale to zero with no charge
The scaling model is the differentiator. App Runner can automatically pause when there is no traffic, making it free during idle periods, something Fargate cannot do natively.
Performance Benchmarks
Real numbers from load testing identical Go APIs deployed across all three platforms:
Cold Start Latency (P95)
| Platform | Cold Start P95 | Warm Response P50 | Warm Response P99 |
|---|---|---|---|
| Lambda (Go) | 180ms | 8ms | 45ms |
| Lambda (Node.js) | 250ms | 12ms | 65ms |
| Lambda (Java) | 1.2s | 15ms | 80ms |
| Fargate | N/A (always warm) | 5ms | 25ms |
| App Runner | N/A (always warm) | 7ms | 35ms |
Sustained Throughput (single instance equivalent)
| Platform | Max RPS | P99 Latency at 80% Load |
|---|---|---|
| Lambda (1000 concurrent) | ~5000 | 45ms |
| Fargate (1 vCPU, 2GB) | ~3000 | 25ms |
| App Runner (1 vCPU, 2GB) | ~2500 | 35ms |
Lambda wins on raw throughput because each invocation gets its own execution environment. But the latency consistency goes to Fargate.
Cost Analysis for Three Workload Profiles
Profile 1: Bursty API (Webhook receiver)
Traffic pattern: 100 requests/hour normally, spikes to 10,000 requests/minute during business events. Average 50K requests/day.
| Platform | Monthly Cost | Notes |
|---|---|---|
| Lambda | $3-5 | Near-perfect fit. Pay only during spikes |
| Fargate | $36-50 | Over-provisioned most of the time |
| App Runner | $15-25 | Good with pause-to-zero between spikes |
Winner: Lambda. This is exactly what serverless was designed for.
Profile 2: Steady Backend API
Traffic pattern: Consistent 200-500 RPS throughout business hours, dropping to 50 RPS at night. Average 20M requests/day.
| Platform | Monthly Cost | Notes |
|---|---|---|
| Lambda | $180-250 | Per-invocation cost adds up |
| Fargate (2 tasks) | $72-100 | Most cost-effective at this load |
| App Runner | $90-120 | Simpler but slightly more expensive |
Winner: Fargate. Consistent load means container efficiency pays off.
Profile 3: Scheduled Jobs
Traffic pattern: Runs 4 times daily, processes data for 5-10 minutes each run.
| Platform | Monthly Cost | Notes |
|---|---|---|
| Lambda | $0.50-2 | Minimal cost, only runs when needed |
| Fargate (Scheduled Task) | $2-5 | Good, but minimum 1-min billing |
| App Runner | N/A | Not designed for batch workloads |
Winner: Lambda. Scheduled tasks are Lambda’s bread and butter.
Decision Flowchart
Follow this text-based decision tree:
START: What is your primary workload type?
│
├─ Event-driven (webhooks, queue processing, S3 triggers)
│ └─ → Lambda
│
├─ HTTP API
│ ├─ Needs WebSockets or gRPC?
│ │ └─ Yes → Fargate
│ │
│ ├─ Traffic pattern?
│ │ ├─ Bursty/unpredictable → Lambda + API Gateway
│ │ ├─ Steady/predictable → Fargate
│ │ └─ Low with idle periods → App Runner
│ │
│ ├─ Latency requirement?
│ │ ├─ Sub-50ms P99 required → Fargate
│ │ └─ 100ms+ acceptable → Lambda or App Runner
│ │
│ └─ Team AWS experience?
│ ├─ None → App Runner
│ ├─ Some → Fargate
│ └─ Deep → Fargate (or Lambda for specific workloads)
│
├─ Batch processing
│ ├─ Under 15 minutes? → Lambda
│ └─ Over 15 minutes? → Fargate (ECS Scheduled Task)
│
└─ Background worker (queue consumer)
├─ Sporadic messages? → Lambda (SQS trigger)
└─ Continuous stream? → Fargate
Migration Paths Between Platforms
Lambda to Fargate
The most common migration I help clients with. Typical triggers:
- Cold starts causing user complaints
- Hitting concurrency limits
- Monthly bill exceeding Fargate equivalent
- Need for WebSocket support
Migration approach:
// Your Lambda handler
func HandleRequest(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// Business logic here
}
// Becomes a standard HTTP handler for Fargate
func HandleRequest(w http.ResponseWriter, r *http.Request) {
// Same business logic, different wrapper
}
The business logic stays the same. You are swapping the invocation wrapper and adding an HTTP server. Typical migration takes 1-2 days for a single service.
Fargate to App Runner
Simpler than you might think. If your service is a straightforward HTTP API without complex networking needs:
- Push your existing Docker image to ECR
- Create App Runner service pointing to that image
- Configure auto-scaling rules
- Update DNS
The catch: you lose fine-grained VPC control, custom health check paths beyond HTTP 200, and task placement strategies.
App Runner to Fargate
When you outgrow App Runner’s limitations:
- Create ECS cluster and service definition
- Set up ALB with health checks
- Configure auto-scaling policies
- Set up VPC networking
- Migrate DNS
More work, but it is a one-time investment that gives you full control.
When to Combine Them: The Hybrid Approach
Most production systems I build end up using multiple compute options. This is the hybrid pattern from my architecture guide:
Core API on Fargate: The service users interact with directly. Needs to be fast, reliable, always available.
Event processors on Lambda: Webhook handlers, SQS consumers, S3 event processors. Bursty and event-driven by nature.
Internal tools on App Runner: Admin dashboards, internal APIs, developer tools. Do not need maximum performance.
Scheduled tasks on Lambda: Daily reports, data cleanup, sync jobs. Run briefly and infrequently.
Example architecture for an e-commerce platform:
User traffic
│
▼
[ALB] ──→ [Fargate: Product API] ← Core API
│
├──→ [SQS] ──→ [Lambda: Order Processing]
├──→ [S3] ──→ [Lambda: Image Resizing]
└──→ [SNS] ──→ [Lambda: Notification Sender]
[EventBridge] ──→ [Lambda: Daily Report Generator]
[CloudWatch] ──→ [Lambda: Cleanup Stale Sessions]
Admin users ──→ [App Runner: Admin Dashboard]
Each component uses the compute option that best matches its workload profile. This is not over-engineering. This is right-sizing.
Practical Recommendations
Based on my experience across dozens of projects:
If you are just starting out: Begin with Lambda. Zero upfront cost, zero infrastructure to manage. Migrate when you hit constraints, not before.
If you have a proven product with steady traffic: Move your hot path to Fargate. Keep everything else on Lambda. The cost savings and performance improvements are immediate.
If you want simplicity above all: App Runner for HTTP services, Lambda for everything else. Less control, less headache.
If you are running DevOps for a small team: The hybrid approach gives you the best cost-to-performance ratio without requiring deep AWS expertise for every component.
The worst decision is no decision. Pick one, ship your product, and optimize later when you have real usage data. I have seen too many teams spend weeks debating Lambda vs Fargate for a product that does not have users yet.
Start simple. Measure. Then optimize.
Need help choosing the right compute strategy for your specific workload? I offer DevOps support where we can analyze your traffic patterns and design an architecture that balances cost, performance, and operational simplicity.