System designCore10 min

Compute models: VMs, containers & serverless

Code has to run somewhere: on a virtual machine you manage, in a container packed onto shared machines, or as a function the platform starts for each request. The choice decides how much you operate, how strongly workloads are isolated, how fast new capacity starts, and whether you pay for machines or for work. Most systems end up using more than one.

1 · The choice

Each model trades control for less to manage

An online course platform runs five kinds of work. A web app and API serve students all day. Instructors upload lecture videos that must be transcoded, sometimes for an hour. Payment-provider webhooks arrive in bursts. Every night, certificates are rendered as PDFs. And students submit code that the platform compiles and runs to grade it, code nobody at the company has read.

Each could run on any of three models. They differ in what you manage and what you pay for:

Aspect

Virtual machine

Container

Serverless function

You manage

OS, runtime, app, scaling

Image and app; an orchestrator places it

Just the code

Isolation

Own kernel

Shared kernel

Platform's choice, often a micro-VM

Starts in

Slowest: a whole OS boots

Faster: a process starts

Fast when warm; a cold start adds delay

You pay for

Running time, busy or idle

The machines underneath

Each request and its duration

Under the labels, all three are the same thing: a Linux process running on a physical server. What changes is how much is wrapped around it, who builds the wrapping, and how billing is measured. Seeing each wrapping makes the trade-offs predictable.

2 · Virtual machines

A virtual machine is a whole computer, with its own kernel

A virtual machine (VM) is an emulated computer run by a hypervisor on a physical host. Each VM boots its own operating system with its own kernel, so a crash or compromise inside one doesn't touch its neighbors except through the hypervisor. That is the strongest isolation of the three, and the traditional way to run different customers' code on the same hardware.

Course web app VMown OS and kernelTranscoder VMown OS, kernel and GPUHypervisorPhysical host

Each VM boots a whole operating system. A crash inside one reaches its neighbors only through the hypervisor.

The price is weight. Each VM carries a full operating system, so it uses more memory and starts slower than a process: a whole OS has to boot before your code runs. And you pay for it while it runs, busy or idle; EC2, for example, bills a running Linux instance per second with a one-minute minimum each time it starts.

For the course platform, VMs suit the steady web app if the team wants full control of the machine, and anything that needs a custom kernel, special hardware such as GPUs for transcoding, or software licensed per machine. Patching the OS, scaling the fleet and replacing failed machines are the team's job (Autoscaling).

3 · Containers

A container is an isolated process sharing the host's kernel

A container is not a small VM. It is an ordinary process that the Linux kernel isolates using two features:

  • Namespaces give it its own view of global resources: its own process IDs, network interfaces, mount points and hostname.
  • Control groups (cgroups) limit and account for what it may use: CPU, memory, I/O.

It starts from an image holding its files and dependencies, so it runs the same on a laptop and in production. All containers on a host share one kernel, which is why many can run on less hardware than the same number of VMs.

Web API containerown PIDs, network, filesPDF worker containerown PIDs, network, filesWebhook containerown PIDs, network, filesOne Linux kernelnamespaces + cgroupsVM or physical hostsystem callssystem callssystem calls

Each container sees only its own processes, network and files, but every system call goes to the same kernel. That shared kernel is both the efficiency and the risk.

An orchestrator such as Kubernetes decides where containers run. Each container declares a request, what it needs, which the scheduler uses to pick a machine with room, and a limit, which the kernel enforces through cgroups. The two limits behave differently: a container at its CPU limit is throttled, slowed down; a container over its memory limit can be killed by the kernel's out-of-memory killer. Set memory limits with headroom.

The shared kernel is the catch. The traditional view is that VMs give strong security at high overhead and containers weaker security at minimal overhead, so containers alone are the weaker choice for code you don't trust, such as the students' submissions. Two ways to harden them: gVisor runs containers on an application kernel written in Go in user space, so the code never talks to the host kernel directly; or each container gets its own micro-VM, later on this page.

4 · Serverless

A function runs per request, and the first request may wait for a cold start

A serverless function, such as AWS Lambda, is code the platform runs when an event arrives: an HTTP request, a queue message, a file upload. There are no servers to size or patch. The platform creates execution environments as needed and bills for each request and for its duration, in milliseconds, scaled by the memory configured.

The webhook handler is a natural fit: idle most of the time, a burst when a sale ends. Lambda gives each concurrent request its own execution environment, so the number it needs is concurrency = requests per second × average duration. At 40 webhooks a second taking 0.2 seconds each, that is 8 environments. The first request into a new environment pays for setting it up:

Cold starts hit under 1% of Lambda invocations, lasting from under 100 ms to over a second, and the setup time is billed. A reused environment keeps its database connections and loaded libraries.

The model has firm edges. A Lambda invocation runs at most 15 minutes and can use up to 10,240 MB of memory, and by default an account may run 1,000 executions at once, a quota that can be raised. Hour-long video transcoding doesn't fit. For latency-sensitive paths, provisioned concurrency keeps a set number of environments initialized in advance, at a price, which trades away part of the pay-per-use model.

Serverless containers, such as Cloud Run, sit in between: you supply a container image, the platform scales it, and it can scale to zero. Unlike Lambda, one instance can serve many requests at once, up to 1,000, by default 80 per vCPU when deployed from the command line, so a burst needs fewer cold starts. A minimum number of instances can be kept warm.

5 · Micro-VMs

Micro-VMs give each workload its own kernel at container speed

Public serverless platforms face the course platform's grading problem at enormous scale: running strangers' code side by side. Containers alone were too weak and classic VMs too heavy, so AWS built Firecracker, a minimal virtual machine monitor. Each workload gets a micro-VM with its own small Linux kernel:

Firecracker micro-VM

Figure

Memory overhead

Under 5 MB per micro-VM

Time to application code

Under 125 ms

Creation rate

Up to 150 micro-VMs per second per host

In production

Lambda since 2018, and Fargate

So the isolation column in the first table is less fixed than it looks: a "serverless function" may run inside a VM-strength boundary, and a "container" can be given one too. For the grading sandbox, the course platform can run each submission in a micro-VM or under gVisor, with a CPU and time limit, and no network access.

6 · The cost

Paying per request wins at low and spiky load, and loses when busy all day

Functions bill for work done; machines bill for time running. Which is cheaper depends on how busy the machine would be. Take the webhook handler at 512 MB and 0.2 seconds per request. At the x86 rate in Lambda's pricing examples, $0.0000166667 per GB-second plus $0.20 per million requests, each request costs 0.5 × 0.2 × $0.0000166667 + $0.0000002 ≈ $0.0000018667. Compare that with a small always-on server, assumed here to cost $50 a month:

1 million requests a month
1.87 USD
10 million
18.67 USD
30 million
56 USD
100 million
186.67 USD
Always-on server (assumed)
50 USD

Monthly function cost = requests × $0.0000018667. It passes the $50 server at about 26.8 million requests a month, an average of about 10 a second.

The crossover moves with every assumption: longer requests, more memory or a cheaper server pull it lower; spikier traffic favors the function, because a server must be sized for the peak while the function pays only for what runs. The arithmetic also leaves out what doesn't appear on the bill: patching, scaling and on-call for the server, and cold starts and platform limits for the function.

7 · In practice

Match each workload to the model that fits its shape

Course platform workload

Shape

Fits

Web app and API

Steady all day, latency-sensitive

Containers on an orchestrator, or VMs

Video transcoding

Bursty, up to an hour, wants GPUs

Containers or VMs on a job queue, scaled to the queue

Payment webhooks

Idle, then bursts; short

Functions

Nightly certificates

Once a day, minutes long

A scheduled container job or function

Grading student code

Untrusted, short

Micro-VMs or gVisor, strict limits

For any workload, the checklist:

  • Start from the shape: steady or spiky, seconds or hours, trusted or not.
  • Prefer the least you have to operate that meets the latency, duration and isolation the workload needs.
  • Keep services stateless, so any model can add and remove copies (Stateless services & sessions).
  • Set container requests and limits, with memory headroom.
  • Watch function limits: duration, memory, concurrency, and cold starts on user-facing paths.
  • Run untrusted code behind a kernel boundary you don't share.

Check yourself

5 questions

1. What makes a container different from a virtual machine?
2. A container exceeds its memory limit. What can happen?
3. Webhooks arrive at 40 a second and take 0.2 seconds each. About how many Lambda execution environments are busy at once?
4. Why is an hour-long video transcode a poor fit for a Lambda function?
5. The platform must run students' untrusted code. Which setup gives the strongest isolation at low overhead?

Takeaways

Remember this

  • VMs, containers and functions differ in what you manage, how strongly workloads are isolated, how fast capacity starts, and what you pay for.
  • A container is an isolated process sharing the host kernel; namespaces isolate it and cgroups limit it.
  • Functions bill per request and duration, start an environment per concurrent request, and have hard limits on duration, memory and concurrency.
  • Micro-VMs such as Firecracker give each workload its own kernel with container-like overhead, which is how serverless platforms run strangers' code.
  • Per-request pricing wins at low and spiky load; always-on machines win when busy all day, before counting the operations work each needs.