Skip to main content

AWS Lambda Versus Containers: Choose From Execution Shape, Not Cloud Fashion

Should you deploy next service as an AWS Lambda function or run it in containers on ECS or Kubernetes. The question usually arrives wrapped in anxiety about making the "modern" choice, as if one option is inherently more sophisticated than the other. The real decision has nothing to do with fashion. It comes down to execution shape: how your code actually runs, how long it needs to run, and what operational complexity you're willing to accept in exchange for specific runtime guarantees.

The Question That Actually Matters: What Does Your Code Do When It Runs?

Lambda and containers solve different problems. Lambda is an event-driven execution model optimized for short-lived, stateless work triggered by discrete events. Containers are long-lived processes that you manage, scale, and monitor as running services. The choice is not about which technology is newer or more impressive on a résumé. It is about matching the execution model to the actual behavior of your workload.

Ask yourself: does your service respond to individual requests, messages, or S3 events and then go idle? Or does it maintain persistent connections, background threads, in-memory state, or long-polling loops? If your service is stateless and event-driven, Lambda removes most of the operational overhead. If your service needs to run continuously, manage its own lifecycle, or hold state in memory between invocations, containers give you the control you need without fighting the platform.

Where Lambda Works Well and Where It Becomes Expensive Friction

Lambda excels at request-response patterns, scheduled jobs, and event processing where each invocation is independent. I have built Lambda functions that process S3 uploads, transform records in data pipelines, and serve REST API endpoints behind API Gateway. The runtime handles scaling, retries, and infrastructure failures. You write the function, configure memory and timeout, and deploy. The cold-start latency is real but manageable for most backend work, especially with ARM64 Graviton processors and provisioned concurrency when milliseconds matter.

Lambda becomes friction when your workload does not fit the fifteen-minute execution limit, when you need persistent WebSocket connections, or when you are running legacy code that expects a long-lived process with mutable global state. I have seen teams try to shoehorn background workers, polling loops, and stateful services into Lambda by adding complex coordination through SQS, DynamoDB, or Step Functions. The result is usually more operational complexity than running the same workload in a container with a process supervisor.

Containers give you full control over the runtime. You decide when the process starts, how it handles signals, and what happens during shutdown. If your service needs to maintain a connection pool, run a gRPC server with streaming, or keep a machine-learning model loaded in memory across thousands of requests, containers are the simpler choice. You pay for continuous uptime, but you avoid the architectural contortions required to make a stateful service work in a stateless execution model.

The Hidden Costs and Operational Tradeoffs

Lambda's promise is reduced operational overhead, and for event-driven workloads that promise holds. You do not provision EC2 instances, configure autoscaling groups, or maintain cluster capacity. But you do accept AWS-specific APIs, hard runtime limits, and a programming model that discourages anything beyond pure functions. If your team values portability or needs to run the same workload on-premises, containers running on ECS, Kubernetes, or even Docker Compose give you a consistent runtime across environments.

Containers require more operational work up front. You build images, push them to a registry, configure orchestration, set resource limits, define health checks, and monitor task failures. ECS simplifies much of this, especially with Fargate, but you still own the runtime lifecycle. Kubernetes offers more flexibility and control but adds significant platform complexity unless your team already has cluster expertise or you are running many services that benefit from shared infrastructure.

Cost is a tradeoff, not a clear winner. Lambda charges per invocation and GB-second of memory. For low-volume workloads or highly variable traffic, Lambda is often cheaper because you only pay when the function runs. For sustained traffic, containers can be more economical because you pay for reserved capacity, not per-request overhead. I have seen Lambda costs grow unexpectedly when request volumes increased or when developers chose generous memory settings without profiling actual usage. Containers give you predictable costs but no automatic scale-to-zero.

A Decision Framework Based on Execution Shape

Use Lambda when your workload is event-driven, stateless, completes in under fifteen minutes, and benefits from automatic scaling without provisioning infrastructure. Examples include API endpoints with low-to-moderate traffic, S3 event processors, scheduled data transformation jobs, and webhook receivers. Lambda is also the right choice when you want to minimize operational overhead and your team does not have container orchestration expertise.

Use containers when your workload requires persistent state, long-running processes, custom runtimes, or fine-grained control over startup, shutdown, and resource allocation. Examples include gRPC services with streaming, background workers with long-polling, services that load large models into memory, and legacy applications that assume a traditional server environment. Containers are also the better choice when portability matters or when you are already running a cluster and the marginal cost of adding another service is low.

One practical pattern I have used is a hybrid approach: Lambda for event ingestion and lightweight processing, containers for stateful services and longer-running workflows. For example, a Lambda function might receive an S3 upload event, validate the file, and publish a message to SQS. A containerized worker then processes the message, runs a heavy transformation, and writes results to Redshift. Each component uses the execution model that fits its shape.

Common Pitfalls

  • Choosing Lambda for long-running batch jobs: If your job regularly approaches the fifteen-minute limit, you will spend more time engineering workarounds than running the workload. Use containers or Step Functions with chunked tasks instead.
  • Underestimating cold-start impact for latency-sensitive APIs: Cold starts are typically 100–500ms for interpreted runtimes and 1–3 seconds for JVM or .NET. If your API has a strict p99 latency requirement, provision concurrency or use containers.
  • Running stateful services in Lambda with DynamoDB as a workaround: If you need shared state across invocations, you are fighting the execution model. A container with an in-memory cache or persistent volume is simpler and faster.
  • Ignoring VPC networking costs when Lambda needs database access: Lambda functions in a VPC incur NAT gateway and data transfer costs. Measure those costs early, and consider whether a containerized service with a persistent connection pool is more economical.
  • Deploying containers without health checks or graceful shutdown: Containers that do not handle SIGTERM correctly or lack proper health checks will cause deployment failures and request errors during rollouts.

The Honest Takeaway

Lambda and containers are not competing philosophies. They are tools optimized for different execution shapes. If your service is stateless, event-driven, and short-lived, Lambda removes operational overhead and scales automatically. If your service is stateful, long-running, or needs fine-grained runtime control, containers give you the flexibility without architectural compromise. The mistake is choosing based on what feels modern rather than what matches the actual behavior of your workload. Start with the execution shape, measure the operational and cost tradeoffs, and pick the runtime that lets your team ship working software without fighting the platform.

Comments