How Does Docker Differ From Virtual Machines?
Table of Contents
The short answer: a virtual machine virtualizes hardware and runs a complete guest operating system, including its own kernel, on top of a hypervisor. A container shares the host machine’s kernel and isolates just the application process, meaning its filesystem, network and process tree, using kernel features (namespaces and cgroups on Linux) rather than hardware virtualization.
That one architectural difference explains nearly every practical difference between the two.
What’s actually running #
Virtual machines Containers
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ App A │ │ App B │ │ App A │ │ App B │
│ Bins/Libs │ │ Bins/Libs │ │ Bins/Libs │ │ Bins/Libs │
│ Guest OS │ │ Guest OS │ └───────────┘ └───────────┘
│ (kernel) │ │ (kernel) │ ┌─────────────────────────┐
└───────────┘ └───────────┘ │ Container runtime │
┌─────────────────────────┐ ├─────────────────────────┤
│ Hypervisor │ │ Host OS (shared kernel) │
├─────────────────────────┤ ├─────────────────────────┤
│ Physical hardware │ │ Physical hardware │
└─────────────────────────┘ └─────────────────────────┘
Each VM carries a full OS: its own kernel, its own device drivers, its own system libraries and utilities. Containers on the same host all share one kernel. The isolation is enforced by that kernel (via namespaces for what a process can see, cgroups for what resources it can use), not by emulating hardware underneath a second OS.
Why this matters in practice #
| Virtual Machine | Container | |
|---|---|---|
| Startup time | Seconds to tens of seconds (boots a kernel) | Well under a second to a few seconds (starts a process) |
| Image size | Gigabytes (full OS) | Megabytes to low hundreds of MB |
| Density per host | Tens of VMs, typically | Hundreds of containers, typically |
| Isolation boundary | Hardware-level (hypervisor) | Kernel-level (namespaces/cgroups) |
| OS flexibility | Any guest OS, independent of host | Must share the host’s kernel (Linux containers need a Linux kernel) |
The density and startup-time difference is why containers reshaped how people deploy: you can run a health check, restart a crashed process, and scale out a new replica in the time it takes a VM to boot its kernel.
Where VMs still win #
Weaker isolation is the real tradeoff, not to be played down. Because containers on one host share a kernel, a kernel-level vulnerability can, in principle, let one container affect another or the host, which a hardware-virtualized VM boundary is much more resistant to. For genuinely untrusted, multi-tenant workloads (running code you don’t control, for customers who don’t trust each other), a VM, or a purpose-built sandboxed runtime such as Firecracker (a micro-VM) or gVisor (a user-space kernel), is the more defensible boundary.
VMs also let you run a different kernel than the host: a Windows VM on a Linux hypervisor, for instance. A container fundamentally cannot do that. Docker Desktop on Mac and Windows works around this by running a lightweight Linux VM under the hood specifically to host the Linux kernel that containers need, which is why “Docker on Mac” is, underneath, a small VM running containers, not native container support.
The reality is: you’ll use both together #
In production, the common pattern is both, layered: cloud VMs (EC2, GCE, Azure VMs) as the underlying compute, running a container orchestrator (Kubernetes, ECS) on top, scheduling containers onto those VMs. The VM gives you the hardware-level isolation boundary between tenants or environments; the container gives you fast, dense, portable application packaging within that boundary. See What Is Docker and Why Should You Use It? for what that packaging actually buys you day to day.