Skip to content

Lambda vs ECS: choosing a compute model

Lambda and ECS solve overlapping problems with very different operational shapes. The comparison below is the starting point; the sections after it deal with the case that comes up most often in practice — an existing application being moved to AWS.

AspectAWS LambdaAmazon ECS
ModelServerless, event-driven functionsContainers, orchestrated as long-running services or tasks
Execution limit15 minutes per synchronous invocationNo time limit
ScalingAutomatic, per functionAutomatic, via service auto scaling and capacity providers
Cost modelPer request and per GB-second of executionFor the Fargate or EC2 capacity consumed
StateStateless between invocationsStateful or stateless
Cold startsYes; mitigated with provisioned concurrencyMinimal once tasks are running
DeploymentFunction versions and aliases, coordinated per functionStandard container image and task-definition rollout
TestingCloud-dependent; integration testing spans functionsConventional container testing
Code sharingLayers, or duplication across functionsNative, inside the image
Resource limitsUp to 10,240 MB memory; 512 MB-10,240 MB of /tmpBounded by the Fargate task size or the EC2 instance
NetworkingVPC attachment supportedNative VPC networking per task
MaintenanceLow per function, higher across a large estate of themModerate; you own the image and its base
Best forEvent processing, glue, discrete short tasksLong-running services, complex applications, existing codebases

When a monolith should go to ECS rather than Lambda

Section titled “When a monolith should go to ECS rather than Lambda”

Migrating an existing monolithic application to Lambda means decomposing it first. That is a rewrite dressed as a migration, and it is rarely what the project was funded to do. ECS lets the application move largely intact and be decomposed later, on its own schedule.

The specific obstacles when the requirement is “keep as much existing code as possible”:

  1. Converting a monolith into functions requires substantial refactoring before anything runs.
  2. A synchronous invocation is capped at 15 minutes, which not every component will fit inside.
  3. Cold starts add latency the current application does not have and has not been designed around.
  4. A large application split across many functions needs deliberate coordination that the monolith got for free from being one process.

Decomposition is worth paying for when it buys independent scaling and independent deployment. It is not free, and the costs land in four places.

Inter-function communication. Functions that need to call each other require an explicit API contract, a transport, retry semantics and a failure story. Inside a container these are method calls.

Shared code. Common libraries must be packaged as Lambda layers or duplicated across deployment packages, and then kept in step. Inside an image they are just dependencies.

State. Anything that was in-process state becomes a round trip to DynamoDB, ElastiCache or Step Functions, with the consistency and idempotency questions that implies.

Deployment. Interdependent functions must be versioned, rolled out and rolled back together. Canary and blue/green strategies that are a single ECS service-update setting become a coordination exercise across many functions, and API Gateway stage versions have to line up with the function versions behind them.

Container images run the same way on a developer machine, in CI and in the cluster, so established testing practice carries over. Lambda’s execution environment does not exist locally in the same form: local emulators approximate it, each cross-function interaction needs mocking, end-to-end tests must orchestrate several invocations, and tracing a failure across function boundaries is harder than following a stack trace inside one process. None of this makes Lambda the wrong choice — it makes it a choice with a testing budget attached, which should be spent deliberately rather than discovered late.