Terminus Expanse
CurriculumBlogPricingSign in
Back to dispatches
guidesdockerAug 15, 2026

Docker vs. virtual machines: what's actually different

Terminus Expanse

The short answer

A virtual machine emulates an entire computer. It gets its own kernel, its own drivers, its own full copy of an operating system, all sitting on top of a hypervisor that pretends to be real hardware underneath. A Docker container shares the host machine's kernel and only isolates what sits above it: the filesystem, the process tree, the network stack. That single difference explains almost everything else people notice: containers start in milliseconds, VMs take tens of seconds; you can run a dozen containers comfortably on a laptop that would struggle to run three VMs.

Why sharing a kernel is such a big deal

An operating system kernel is the part that talks directly to the CPU, memory, and disk. Booting a VM means booting an entire second kernel from scratch, the same way your laptop boots when you turn it on, just inside a simulated box. A container never does that. When you run docker run ubuntu, Linux doesn't boot a new Ubuntu kernel — it uses namespaces and cgroups, two features already built into the Linux kernel, to make one process (or a small group of them) believe it has its own filesystem, its own hostname, its own process list, and its own slice of CPU and memory, while actually running on the exact same kernel as the host.

This is also why Docker on Linux is native and fast, while Docker on Mac or Windows quietly runs a small Linux VM in the background for containers to share — because there's no Linux kernel to share with otherwise. If you've ever wondered why Docker Desktop on a Mac uses noticeably more memory than the containers themselves seem to need, that's the hidden VM doing the kernel-sharing that Linux gets for free.

What you actually give up with a container

Isolation, but not as much of it. A VM's isolation is enforced by the hardware itself — the hypervisor uses CPU virtualization features to make sure one VM genuinely cannot see or touch another's memory, even if the guest OS inside it is compromised. A container's isolation is enforced by the host kernel's namespace and cgroup rules — strong in practice, but a kernel-level exploit that escapes a container has a much shorter path to the host than one escaping a VM. That's the real reason security-sensitive multi-tenant platforms (think: a cloud provider running strangers' code) still reach for VMs, or for containers running inside a VM, rather than bare containers alone.

You also lose OS flexibility. A VM can run Windows on a Linux host, or an old kernel version next to a new one, because each VM genuinely has its own kernel. A container is always running on the host's kernel, so you can change the distribution (an Ubuntu container on a Debian host is fine — it's just files and a package manager) but not the kernel itself. This is why there's no such thing as a "Windows container" running on a Linux host, no matter what the container image claims to contain.

Why this ended up mattering so much for how software gets shipped

Before containers were common, "it works on my machine" was a real, constant problem — a Python app that worked fine on a developer's laptop would break in production because of a different library version, a missing system package, or an OS difference nobody thought to check. A container image bundles the application and everything it depends on — right down to the specific version of every library — into one artifact that behaves identically wherever it runs, because it's not relying on the host to already have the right things installed. A VM image can do this too, technically, but at the cost of shipping an entire OS every time, which is slow to build, slow to start, and slow to move around a network.

That combination — fast startup, small images, identical behavior everywhere — is why a single server can now run dozens of small, independent services in containers instead of a handful of monolithic VMs, and why "containerize the app" became such a common first step when a team decides to modernize how they deploy software.

When you'd still reach for a VM

Containers didn't make VMs obsolete — they just moved VMs to a different layer. Cloud providers still run your containers inside VMs, because that's where they get the hardware-enforced isolation between different customers' workloads. If you need to run a different operating system entirely, or you need the strongest possible isolation for something untrusted, a VM is still the right tool. Containers solved a specific, extremely common problem — "package and run software consistently, fast, without the overhead of a full OS" — without replacing the other reasons virtual machines exist.

Understanding this distinction is also the fastest way to stop being confused by half the vocabulary around modern infrastructure: a Kubernetes "node" is usually a VM, running many containers; a "pod" is a small group of containers that share a network namespace; and when someone says "serverless," what's actually running your code is, more often than not, still a container — just one you never had to think about starting.

This post is about