Serverless vs Containers vs VMs: When to Use Each
The three compute primitives of modern cloud infrastructure aren't competitors — they're tools for different jobs. Most production architectures use all three.
Multivak Labs
Engineering Team
Every few years a new compute paradigm arrives and people declare the previous ones dead. VMs were supposed to die when containers emerged. Containers were supposed to die when serverless arrived. None of them died. Each solved a different problem, and production systems that understand that tend to use all three together.
Virtual Machines — The Reliable Foundation
A VM gives you a full operating system on dedicated (or virtualised) hardware. You control the kernel, the OS configuration, the network stack, and everything that runs on it. That control comes with responsibility: you patch the OS, you manage the runtime, you capacity-plan the instance size.
Best for:
- Stateful workloads that require persistent local disk with specific performance characteristics
- Legacy applications that weren't designed to run in containers
- Licensed software with hardware-level affinity requirements
- Databases that need fine-grained OS and I/O tuning (PostgreSQL on EC2 with custom kernel parameters, for example)
- Workloads that must run continuously and where cold-start latency is unacceptable
Not great for: teams that want to ship fast without managing infrastructure. VM fleets require patching, AMI baking, and ops discipline that containers abstract away.
Containers — The Portability and Density Play
Containers share the host OS kernel but isolate the application runtime. They start in milliseconds, pack more workloads onto the same hardware, and produce the same behaviour in dev, staging, and production. The containerisation mental model — your app is a self-contained image with a well-defined interface — forces good engineering habits.
Best for:
- Microservices and APIs that deploy frequently
- Batch processing jobs that need consistent environments
- Teams running CI/CD pipelines that need repeatable build and test environments
- Applications that need to scale horizontally across many instances
- Any workload where "works on my machine" has been a problem
Not great for: applications that require deep OS access, workloads with spiky traffic patterns where you're paying for idle containers, or teams without Kubernetes experience trying to manage a cluster themselves.
Serverless — The Zero-Ops Option
Serverless functions (Lambda, Cloud Functions, Azure Functions) eliminate infrastructure management entirely. You upload code; the platform handles execution, scaling, and availability. You pay per invocation, not per idle instance. At zero load, you pay nothing.
Best for:
- Event-driven workflows — webhook handlers, S3 event processors, queue consumers
- Infrequent tasks where paying for a running container or VM doesn't make sense
- API endpoints with highly variable or unpredictable traffic
- Glue code between services that doesn't warrant its own container
- Scheduled jobs (cron replacement)
Not great for: long-running processes (most serverless platforms cap at 15 minutes), latency-sensitive workloads where cold starts matter, stateful applications, or workloads with consistently high concurrency where per-invocation pricing exceeds reserved instance cost.
The Decision Framework
When evaluating a workload, ask four questions:
- Does it need to run continuously, or is it event-triggered? Continuous → VM or container. Event-triggered → serverless first.
- Is traffic predictable or spiky? Predictable → containers or VMs (reserved capacity is cheaper). Spiky → serverless scales to zero.
- How much OS-level control do you need? High → VM. Moderate → container. None → serverless.
- What's the team's operational appetite? Low → serverless. Medium → managed containers (ECS, Cloud Run). High → self-managed Kubernetes.
Hybrid Architectures That Actually Work
Real production systems use all three. A common pattern: API Gateway routes requests to Lambda functions for lightweight endpoints → ECS containers handle compute-heavy processing jobs → RDS on EC2 for the database with tuned storage I/O. Each layer uses the right primitive for its workload.
The goal isn't architectural purity — it's matching the cost, operational complexity, and performance characteristics of each compute primitive to the workload it hosts.
If you're designing a new system or re-evaluating an existing architecture, start with workload characteristics — not with which service is trendy. The answer is almost always a combination of all three.
Designing cloud architecture for a new product or migration? See how we approach cloud infrastructure — from compute strategy through IaC, cost optimisation, and managed operations.