Containers vs VMs: What You're Actually Trading
Both isolate workloads, but at different layers and with different costs. A clear-eyed look at the boundary, the security implications, and when each one wins.
The Problem
"Just put it in a container" and "spin up a VM" get used interchangeably, but they isolate workloads at completely different layers. Choosing wrong means either paying for isolation you don't need or assuming an isolation boundary you don't actually have.
Why It Matters
The boundary you pick determines your density, your startup latency, and — most importantly — your blast radius if something escapes. Treating a container like a VM for security is a mistake that shows up in incident reviews.
Core Concepts
A virtual machine virtualizes hardware. A hypervisor runs multiple guest operating systems, each with its own kernel. The isolation boundary is the hypervisor, and it's strong.
A container virtualizes the operating system. Containers share the host kernel and are isolated by kernel features — namespaces (what a process can see) and cgroups (what it can use). The boundary is the kernel, which is lighter but a larger attack surface.
Implementation
A container image is just your app plus its userspace dependencies — no kernel:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node # don't run as root
EXPOSE 8080
CMD ["node", "server.js"]
This starts in milliseconds and ships as tens of megabytes because it reuses the host kernel. A VM image carries a full OS and boots in seconds.
Common Mistakes
- Running containers as root. A root process that escapes a namespace is root on
the host. Set a non-root
USER. - Assuming container = security boundary. Shared-kernel isolation is weaker than a hypervisor. For hostile multi-tenant workloads, you want stronger isolation.
- Fat images. Copying the whole build context and dev dependencies bloats images and widens the attack surface. Use multi-stage builds and slim bases.
Production Considerations
When you need both container ergonomics and VM-grade isolation — running untrusted code, say — reach for a sandboxed runtime like Firecracker microVMs or gVisor. They give each workload a tighter boundary while keeping fast startup, which is why serverless platforms use them under the hood.
Security
Drop Linux capabilities you don't need, mount filesystems read-only where possible, and never mount the Docker socket into a container — that's root on the host by another name. Scan images for known vulnerabilities in CI.
Performance
Containers win on density and startup: you can pack many on a host and start them near-instantly, ideal for scaling stateless services. VMs cost more per workload but give stronger isolation and let you run different kernels or operating systems on the same hardware.
Summary
VMs virtualize hardware with a strong hypervisor boundary; containers virtualize the OS with a lighter, larger-surface kernel boundary. Use containers for dense, fast-scaling services you trust, VMs (or microVMs) when isolation strength matters more than density. Match the boundary to the threat, and don't mistake one for the other.
The weekly engineering digest
Production-grade engineering writing in your inbox. No spam, unsubscribe anytime.