← All EduBytes

Docker containers are not lightweight VMs

1 September 2026 · 4 min read

The comparison is how almost everyone is introduced to containers, and it is wrong in three ways that decide what you can run, what breaks on upgrade, and how strong the isolation actually is.

Almost everyone meets containers through the same sentence: they are like virtual machines, but lighter. It is a good enough starting point that most people never replace it, and it is wrong in three ways that decide what you can run, what breaks when you upgrade, and how much the isolation is actually worth.

What the two things actually are

A virtual machine runs on a hypervisor that presents virtual hardware — a virtual CPU, virtual disks, a virtual network card. On top of that hardware you boot a complete operating system, including its own kernel. Two VMs on one host are two operating systems that happen to share a machine, and neither can directly perceive the other.

A container is a process. It runs on the host’s kernel, alongside every other process, and what makes it feel separate is a set of kernel features applied to it:

That is essentially the whole trick. A container image contains a filesystem and no kernel, which is why images are small and why they start in milliseconds: no hardware to emulate, no operating system to boot. You are starting a process.

Consequence one: there is only ever one kernel

Everything running on the host shares it.

So you cannot run a Windows container on a Linux host, or the reverse, without a VM somewhere in the picture. And you cannot use a kernel feature the host kernel does not have, no matter what the image contains. A container built against a newer kernel does not carry that kernel with it.

This is also why “it works in a container, so it works everywhere” is weaker than it sounds. Containers make the userland reproducible — libraries, binaries, configuration. The kernel remains a property of the host, so a kernel upgrade underneath your containers is a change to their environment even though nothing in the image moved.

Consequence two: the isolation boundary is much larger

A VM’s guest talks to the hypervisor through a deliberately narrow interface. Escaping a VM means finding a bug in that small, heavily-scrutinised surface.

A container talks to the host kernel through the system call interface — hundreds of calls, decades of accumulated behaviour, and the same code path every other process on the machine uses. A container escape is a kernel vulnerability, and kernel vulnerabilities are found regularly.

This does not make containers unsafe; it makes them a different strength of boundary, and the difference matters when you are choosing what to put next to what. Two services from your own team, fine. Arbitrary code submitted by strangers, and the answer changes — which is precisely why gVisor, Kata Containers and Firecracker exist. Every one of them is an attempt to put a real kernel boundary back underneath a container-shaped interface, and nobody would have built them if a container were already a lightweight VM.

Consequence three: limits are accounting, not hardware

A VM given 2 GB of RAM has 2 GB of RAM. Its guest kernel knows that, and every program inside it sees a machine with 2 GB.

A container given a 2 GB cgroup limit is a process on a machine with, say, 64 GB — and much of what runs inside it will happily read the host’s numbers. This produced years of real production incidents: JVMs sizing their heap from total host memory and getting killed, runtimes reading the host’s CPU count and sizing thread pools for a machine they cannot use, free and top inside a container reporting the host. Modern runtimes have mostly learned to read cgroup limits instead, but the underlying reason is unchanged — the limit is bookkeeping the process has to consult, not hardware it inevitably observes.

The memory limit is enforced, mind you. Exceed it and the kernel’s OOM killer ends the process. It is just that nothing told the process the limit existed.

Why the confusion is so durable

Partly because “lightweight VM” is a genuinely useful first approximation. Both things give you an isolated filesystem, an isolated network, and a way to run software that doesn’t match the host.

But mostly, I think, because of Docker Desktop. On macOS and Windows there is no Linux kernel to share, so Docker runs a Linux virtual machine and puts your containers inside it. Someone learning containers on a Mac is, quite literally, running them in a VM. The intuition that a container is a small VM gets built on a machine where a VM is genuinely present, and then travels to Linux where it stops being true and nothing announces the change.

The sentence that replaces it

A container is a process on the host kernel with a restricted view of the system and a budget.

Nearly everything follows from that. Fast start, because a process starts fast. Small images, because there is no kernel in them. Shared-kernel constraints, because the kernel is shared. A weaker boundary, because the boundary is the syscall interface. And limits that must be read rather than observed, because they are accounting rather than hardware.