Serverless vs Containers: Which Architecture to Choose?
Cloud

Serverless vs Containers: Which Architecture to Choose?

February 23, 20259 min readServerlessConteneursArchitecture

Serverless and containers each have their place. Here is a pragmatic comparison to help you choose — or combine both — based on your real constraints.

The False Dichotomy

The question "serverless or containers?" starts from a flawed premise. In 2025, modern cloud architectures almost always combine both paradigms, selecting each based on the characteristics of the individual workload. The goal is not to pick a side, but to recognise which tool best serves each component. This guide provides the decision framework we use with our clients.

Fundamental Characteristics of Each Paradigm

Serverless (Lambda, Cloud Run, Azure Functions)

Serverless executes your code in response to events in ephemeral environments. You pay only for actual invocations — zero cost at idle. But this model comes with hard constraints:

  • Execution time limit: 15 minutes maximum on AWS Lambda, 60 minutes on Cloud Run
  • Cold starts: 50 ms to 3 seconds depending on the runtime and package size, on every first invocation after a period of inactivity
  • Per-invocation billing: very cost-effective for sporadic traffic, potentially expensive for sustained high-frequency traffic
  • Automatic scaling: from 0 to thousands of concurrent executions without configuration — both the strength and the risk (an infinite loop can be costly)
  • Ephemeral storage: /tmp limited to 512 MB (10 GB with Lambda SnapStart), no state between invocations

Containers (ECS Fargate, GKE, AKS, Cloud Run Jobs)

Containers run in persistent environments. The process starts once and stays alive to serve multiple requests, offering radically different characteristics:

  • No duration limit: long-running processes (ML inference, streaming, WebSockets) are native
  • Predictable latency: no cold starts, persistent database connections (connection pooling)
  • Full control: custom runtime, system libraries, environment variables, fine-grained network configuration
  • Hourly billing: fixed cost even without traffic — cost-effective only with high utilisation rates
  • Configured scaling: HPA or ECS Service Auto Scaling — scaling configuration is your responsibility

Decision Framework — 8 Criteria

Criterion Serverless Containers
Traffic patternSporadic / unpredictable spikesContinuous / predictable
Latency requirementsP99 >200 ms acceptableP99 <100 ms required
Execution duration<15 minUnlimited
State & connectionsStateless onlyStateful, persistent connections
Cost modelPay-per-use (optimal <5M req/month)Reserved capacity (optimal >50M req/month)
Team expertiseFunction-oriented developersKubernetes/Docker skills
Vendor lock-inHigh (proprietary triggers)Low (Docker standard)
Local developmentDifficult (SAM, emulators)Simple (docker-compose)

Recommended Hybrid Architecture

The most effective pattern we deploy uses Lambda for everything asynchronous and event-driven, and Fargate for latency-critical synchronous APIs. Here is a concrete e-commerce example:

# Hybrid e-commerce architecture

# Synchronous layer (Fargate) — latency-critical
Main REST API              → ECS Fargate (3 tasks minimum, ALB)
Authentication service     → ECS Fargate (persistent DB connections)
WebSocket chat             → ECS Fargate (long-lived connections)

# Asynchronous layer (Lambda) — event-driven
S3 PUT product image       → Lambda resize + WebP conversion
SQS order placed           → Lambda validation + email send
EventBridge cron 02:00     → Lambda nightly report generation
SNS payment notification   → Lambda inventory update
DynamoDB Streams           → Lambda sync to ElasticSearch

# Batch layer (Fargate Spot) — cost-optimised
Daily analytics            → ECS Fargate Spot (45-min task)
Customer CSV export        → ECS Fargate Spot (task > 15 min)
ML batch inference         → ECS Fargate GPU (heavy task)

Cold Start Mitigation Strategies

Cold starts remain the main barrier to serverless adoption for latency-sensitive APIs. The available solutions in 2025:

# Option 1: Provisioned Concurrency
# Keeps N instances warm — cost: ~$0.015/GB-h
resource "aws_lambda_provisioned_concurrency_config" "api" {
  function_name                  = aws_lambda_function.api.function_name
  qualifier                      = aws_lambda_alias.live.name
  provisioned_concurrent_executions = 5
}

# Option 2: Lambda SnapStart (Java only)
# Snapshots the post-init state — cold start reduced to ~100ms
resource "aws_lambda_function" "java_api" {
  runtime    = "java21"
  snap_start {
    apply_on = "PublishedVersions"
  }
}

# Option 3: Optimised container image (Node.js)
# Smaller image = faster cold start
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
# Image < 50 MB = cold start < 200ms

Cost Comparison: 10 Million Requests per Month

Scenario 1 — sporadic traffic (peaks 10x baseline):

# Lambda (512 MB, 300 ms average duration)
Compute  : 10M × 0.3s × 0.0000000083 $/GB-s × 0.5 GB = $0.01
Requests : 10M × 0.0000002 $                          = $2.00
Lambda total: ~$2/month

# Fargate (0.25 vCPU, 0.5 GB, 1 task running 24/7 to absorb spikes)
vCPU    : 0.25 × $0.04048/h × 730h = $7.39
Memory  : 0.5  × $0.004445/h × 730h = $1.62
Fargate total: ~$12/month (1 task)

→ Lambda is 6x cheaper for sporadic traffic

Scenario 2 — sustained traffic (100 req/s continuously):

# Lambda (512 MB, 200 ms, 259M req/month)
Compute  : 259M × 0.2s × 0.0000000083 × 0.5 = $0.21
Requests : 259M × 0.0000002                  = $51.80
Lambda total: ~$52/month

# Fargate (2 tasks at 1 vCPU to handle 100 req/s)
vCPU    : 2 × 1 × $0.04048 × 730 = $59.10
Memory  : 2 × 2 × $0.004445 × 730 = $12.98
Fargate total: ~$72/month (or ~$50 with Savings Plans)

→ Comparable cost, but Fargate delivers lower latency and persistent connections

Advanced Serverless Patterns

Serverless shines in asynchronous architectural patterns that containers handle less elegantly:

  • Fan-out: one SNS event simultaneously triggers N Lambda functions in parallel — distributed processing without orchestration
  • Saga (Step Functions): orchestration of distributed transactions with automatic compensation on failure
  • Event Sourcing with EventBridge: business events published to EventBridge trigger Lambda consumers without direct service coupling
  • CQRS: DynamoDB Streams → Lambda to project writes to optimised read models (ElasticSearch, Redis)

Advanced Container Patterns

  • Sidecar: proxy container (Envoy, AWS App Mesh) attached to the application container to handle TLS, retries, and circuit breaking
  • Service mesh: Istio or Linkerd for inter-service observability and security without modifying application code
  • Blue/Green with Fargate: ECS + CodeDeploy for zero-downtime deployments with instant rollback
  • Spot Instances: ECS Fargate Spot for interruption-tolerant workloads — 70% cost saving on batch tasks

Conclusion: Choose by Traffic Shape, Not by Hype

In 2025, the answer to "serverless or containers?" is almost always "both, depending on the workload." The golden rule: analyse the traffic shape of each component, not its technical complexity. An S3 event that triggers processing three times per hour → Lambda without hesitation. An API receiving 200 requests per second continuously with persistent PostgreSQL connections → Fargate without hesitation. The mistake is forcing a single paradigm onto the entire architecture for the sake of simplicity. A well-designed architecture embraces this heterogeneity as a strength, assigning the right tool to each component and letting workload characteristics — not trends — drive every decision.

← Back to blog