
FORK 02/Rent what?/Containers vs. Functions — A Real Choice
Containers vs. Functions — A Real Choice
Neither is universally better. The decision reduces to workload shape, and workload shape is measurable.
The Axis That Actually Matters
Container orchestration and serverless functions solve the same surface problem — running code without managing physical servers — but they expose very different control surfaces, and those differences are consequential once you move past toy workloads.
Containers, whether you are running them on a managed Kubernetes service or a simpler container-app platform, are persistent processes. You define an image, the runtime spins up an instance, and that instance stays alive, accepting work, warming caches, holding connections. You pay for the instance's lifespan regardless of whether it is busy. The latency profile is flat: the hundredth request costs roughly the same as the first.
Functions-as-a-service work the opposite way. The platform allocates a small execution environment in response to an invocation, runs your code, and may or may not keep that environment warm for the next call. If it doesn't — or can't, under load bursts — you pay the cold-start penalty: container image pull, runtime initialisation, your own application bootstrap, all serialised on the critical path. The trade-off is precise billing: you pay per invocation and per millisecond of execution, not for idle time.
That trade-off only favours you when the workload is genuinely sparse or deeply spiky. If you are calling a function a thousand times a minute with a steady rhythm, the per-invocation billing model will cost more than a container that stays warm and processes a queue, and the cold-start variance will make your p99 latency ugly. The idle tax hurts containers; the cold-start tax hurts functions. The question is which tax applies to your traffic pattern.
Matching Workload Shape to Runtime Model

Run duration is the first filter. Functions impose hard execution limits — typically measured in minutes, occasionally extendable, but fundamentally hostile to long-running work. Video transcoding, database migrations, multi-step ETL pipelines: none of these belong in a function runtime. Containers have no such ceiling; a process runs until you stop it or it crashes.
Invocation pattern is the second filter. Sporadic, event-driven invocations that arrive unpredictably — a webhook from a payment processor, an image resize triggered by an upload, a scheduled nightly report — are exactly what functions are designed for. The platform scales to zero between events, and you pay only for the work done. A container sized to handle these peaks would sit idle most of the day, running up charges without running useful code.
State requirements sharpen the picture further. Functions are, by design, stateless: assume the execution environment will not persist between calls. You can work around this with external caches, databases, or shared storage, but every workaround adds latency and complexity. Containers carry state more naturally — in-memory caches, open database connection pools, local file descriptors — because the process is long-lived. Applications that maintain websocket connections, hold authentication sessions in memory, or rely on warm JVM caches are poor candidates for a function runtime.
Cold-path tolerance is the final and often under-examined filter. Some workloads simply cannot absorb cold-start latency. A user-facing API that must respond in under a hundred milliseconds cannot rely on a function that might spend a second or more initialising a heavy runtime. Provisioned concurrency — keeping a set of function instances pre-warmed — closes that gap but also erodes the cost advantage: you are now paying for reserved capacity, which is structurally similar to paying for a container that stays alive. At that point, a container is the more honest and often cheaper choice.
Cold starts vary enormously by runtime. A small Go or Rust binary initialises in tens of milliseconds. A JVM-based function loading a Spring context may take several seconds. The language and framework choice is not separable from the platform choice.
Where the Lines Blur
Functions impose hard execution limits — typically measured in minutes, occasionally extendable, but fundamentally hostile to long-running work.
Managed container platforms have pushed toward function-like ergonomics — scale-to-zero, per-request billing, event triggers — narrowing the gap. Some workloads fit comfortably in either model once configured correctly. A lightweight HTTP service that sees moderate but uneven traffic might land well on a container platform that scales to zero as easily as it might on a function runtime, and the choice then comes down to operational familiarity and ecosystem fit rather than fundamental architectural correctness.
The interesting edge is composition: many production systems use both. Containers host the long-running, stateful, latency-sensitive core. Functions handle the peripheral event-driven tasks — file processing, notification dispatch, scheduled maintenance — where the sporadic nature genuinely suits the billing and operational model. Treating this as a binary architectural religion misses the point. Serverless is not no server; containers are not bare metal. Both are managed abstractions with different cost and capability envelopes.
The useful question is not which model is better. It is: what is the actual invocation frequency, expected duration, latency budget, and state profile of this specific workload? Answer those four things honestly, and the right choice becomes obvious most of the time. Where it doesn't, the workload probably sits on the boundary — and either choice, done well, will work.