When to choose serverless, containers, or Kubernetes
The runtime should follow the workload, not the trend. Here is the decision rule, plus how to migrate off a monolith and keep event flows safe under real load.
The short answer
Choose the runtime that matches the workload, not the one that looks best on a slide. Serverless (Lambda) is the default for spiky, event-driven, and glue work where you want the operational load near zero. Containers (ECS on Fargate) earn their place for long-running, latency-sensitive processing. Kubernetes is justified only when you genuinely need its scheduling, multi-team platform, or portability, and you have the people to run it. I will not suggest Kubernetes when Lambda, SQS, and Step Functions solve the problem with less to operate.
The rest of this page is how I make that call, and how I keep the event-driven parts safe under load, drawn from a KFC Thailand platform doing 5M+ orders a month and an analytics platform handling 1M+ requests a minute.
The decision in one rule
Map the runtime to the shape of the work. If traffic is spiky or event-driven and each unit of work is short, serverless wins on cost and operations. If the work is long-running or needs steady, predictable latency, containers win. If you need Kubernetes-specific scheduling or a shared internal platform across many teams, and you can staff the control plane, Kubernetes earns it. Everything below is that rule applied to real cases.
When serverless is the default
Reach for Lambda first when traffic is variable, the work is short, or you are gluing services together: API handlers at unpredictable load, S3 and EventBridge triggers, scheduled jobs, and fan-out. Per-request billing means you pay nothing when idle, which is exactly wrong for a service that is busy all day and exactly right for one that spikes. On a SaaS platform I built, Lambda ran the API handlers while ECS ran the long jobs; cold starts on the few hottest endpoints were handled with provisioned concurrency, a small fixed cost well worth the latency it bought.
When containers earn their place
Move to containers when the work is long-running or needs predictable latency that a cold start would spoil. On the KFC Thailand migration, order processing ran on ECS and EKS rather than pure Lambda precisely because the workflows were long-lived and latency had to stay tight under promotion-hour spikes; the result held P99 under 200ms and cut infrastructure cost by roughly 30% against the old monolith. A right-sized Fargate task that runs continuously is usually cheaper than the same load on per-millisecond billing, with fewer latency surprises.
When Kubernetes is justified
Kubernetes is a platform, not a runtime you switch on and forget. It earns its keep when you need its scheduler, a multi-team internal platform, or portability across environments, and when you have engineers to run the control plane and node upgrades. On an analytics platform handling 1M+ requests a minute, we ran ECS for the simpler services and EKS only where teams needed Kubernetes-native tooling. Adopting EKS to run a handful of CRUD services buys you operational cost with no matching benefit.
Migrating without a rewrite
A big-bang rewrite bets the whole system on one cutover. The Strangler Fig pattern avoids that: route traffic endpoint by endpoint from the monolith to new services behind an API Gateway, keeping each step reversible. On the KFC Thailand platform we mapped every endpoint, found that a handful of hotspots drove most of the latency, and moved the low-risk, high-visibility paths first to build confidence before touching checkout. Both systems ran in parallel for weeks so we could compare error rates and latency before fully cutting over.
Idempotent events and DLQs
Event-driven systems deliver messages more than once and interrupt consumers mid-flight, so correctness depends on idempotency. Design every consumer to apply the same event twice with no extra effect, using idempotency keys on the operations that write or charge. On the analytics platform, Fargate Spot cut compute by around 70% at under 2% interruption, but those interruptions caused data gaps until we added idempotency keys and SQS retry logic. Put a dead-letter queue behind every async consumer and alarm on its depth; on the KFC platform, a CloudWatch alarm on consumer lag caught a delay in order confirmations that would otherwise have been silent.
Hot path and cold path
Separate the work that must be fast from the work that can be slow, so they do not fight for the same resources. On the analytics platform, isolating the real-time audit logging (the hot path) from report generation (the cold path) was the single most impactful decision: it took a pipeline that used to lag by minutes down to near-real-time and removed the resource contention that used to hit at peak. The pattern generalizes: keep the user-facing request path lean and push heavy, deferrable work onto a queue and a separate worker pool.
Failure modes, security, cost
Design for the failures each runtime brings. On Lambda, set reserved concurrency so one function cannot starve the account, and give each function a least-privilege IAM role so its blast radius is the permissions, not the code. On containers, own the patching and image supply chain. On Kubernetes, budget for control-plane and node operations as real, recurring cost. Put a VPC around functions only when you need it, since NAT gateways add cost and cold-start weight. Blue-green with a fast rollback is the safety net across all three; on the KFC migration one bad service release rolled back in seconds with no customer impact.
A reference shape on AWS
A pragmatic AWS-native event-driven shape: S3 and EventBridge events fan into Lambda for glue and short work; SQS sits between producers and consumers with a dead-letter queue and an alarm on each; Step Functions orchestrate multi-step flows so retries and timeouts are explicit rather than buried in code. Long-running or latency-sensitive processing runs on ECS or Fargate, and Kubernetes appears only where a team has a real need and the staffing for it. Deployments are blue-green with a rollback measured in seconds, so a bad release is an inconvenience, not an incident.
Common mistakes
- Adopting Kubernetes for a workload that Lambda and SQS would run with less to operate.
- Running steady, all-day, long jobs on per-millisecond billing and calling it cheap.
- Rewriting a monolith in one cutover instead of stranglering it endpoint by endpoint.
- Writing event consumers that are not idempotent, so retries double-apply.
- Shipping async consumers with no dead-letter queue and no alarm on it.
- Letting the hot request path share resources with heavy batch work.
Next step
Have the same problem on your stack?
Send the architecture, AWS bill concern, deploy pain, or GenAI reliability issue. I'll find the first real bottleneck and propose a small, reversible fix.
FAQ
Serverless or Kubernetes for a new project?
Default to serverless unless you have a specific reason not to, and the team to run Kubernetes if you do. For most new projects, Lambda, SQS, and Step Functions ship the same behavior with a fraction of the operational load. Kubernetes is a platform you have to staff and patch; adopt it when you genuinely need its scheduling and portability, not because it is the default in a reference architecture you read.
When does Lambda become more expensive than containers?
When the work is long-running and the traffic is steady. Lambda bills per request and per millisecond, which is cheap for spiky and event-driven work and expensive for a service that is busy all day. Once a workload runs continuously at high throughput, a right-sized ECS or Fargate task is usually cheaper and gives you more predictable latency. The switch point is about duration and steadiness, not request count alone.
How do I migrate a monolith without a rewrite?
Use the Strangler Fig pattern: route traffic endpoint by endpoint from the monolith to new services behind an API Gateway, starting with a low-risk, high-visibility path. On a KFC Thailand platform I worked on, we mapped every endpoint, moved the highest-latency ones first, and ran both systems in parallel for weeks before cutting over. A big-bang rewrite risks the whole business at once; the strangler approach keeps every step reversible.
How do I make event processing safe?
Design consumers to be idempotent, put a dead-letter queue behind every async consumer, and alarm on it. Events get delivered more than once and consumers get interrupted, so a handler that is not idempotent will double-charge or double-write under load. Idempotency keys plus a DLQ mean a failed or duplicate event is caught and replayable instead of silently lost or applied twice.
Do we actually need EKS?
Only if you need Kubernetes-specific scheduling, a multi-team internal platform, or portability across clouds, and you have people to operate the control plane and nodes. Plenty of teams run EKS for a handful of services that ECS on Fargate would run with far less overhead. On one analytics platform we ran both: ECS for the simpler services, EKS only where teams needed Kubernetes-native tooling. Match the runtime to the need, not the resume.
Related reading

Rahul Ladumor
Principal Cloud & AI Platform Architect. AWS Professional certified, 4x AWS Community Builder. I work with teams that have real users, real AWS bills, and real production pressure.
About Rahul →