The purpose of containerisation (Docker) and VMs is to isolate an application and its dependencies from it’s environment so as to avoid dependency conflicts.


How Docker Isolates Processes

The three primitives Docker combines:

chroot + namespaces + cgroups = a container
  • chroot / pivot_root — points the process at a different root filesystem (the image)
  • namespaces — controls what the process can see (its own PID tree, network stack, mounts, hostname)
  • cgroups — controls how much it can use (CPU, memory, I/O)

The result is a normal Linux process that makes normal system calls, is scheduled by the real kernel scheduler, and shares the host’s kernel. There is no second kernel anywhere.

Docker does not virtualise hardware. Docker does not virtualise a kernel. It asks the kernel to present a filtered view of itself to a process.


How a VM Isolates Processes

A virtual machine takes a completely different approach. Instead of asking the kernel for isolation, it replaces the kernel entirely — for each VM.

A hypervisor (KVM, VMware, Hyper-V) runs on the host and presents each VM with emulated hardware: virtual CPUs, virtual RAM, virtual disks, virtual NICs. The VM boots a full OS on top of that virtual hardware, completely unaware it’s not running on real metal.

your application
      ↓
guest userspace (libc, runtime, etc.)
      ↓
guest kernel (its own Linux, Windows, etc.)
      ↓
hypervisor (virtualises hardware)
      ↓
host kernel
      ↓
real hardware

Everything above the hypervisor is fully duplicated per VM.


Side-by-Side Comparison

ContainerVirtual Machine
KernelShared with hostEach VM has its own
OSHost OS onlyFull guest OS per VM
Isolation mechanismNamespace + cgroup (kernel features)Hardware virtualisation (hypervisor)
Boot timeMilliseconds (just a process)Seconds to minutes (full OS boot)
Image sizeMegabytes (userspace only)Gigabytes (full OS + kernel)
Memory overheadMinimal (no guest kernel)Significant (each guest OS uses RAM)
DensityHundreds per host is normalTens per host is typical
Syscall pathDirect to host kernelGuest kernel → hypervisor → host kernel
Kernel compatibilityMust match host kernel ABIAny kernel, any OS
Security boundaryNamespace escape = host accessVM escape is extremely difficult

The Security Trade-off

This is the most important practical difference.

A container shares the host kernel. If an attacker exploits a kernel vulnerability from inside a container, they are already talking to the real kernel — the only barrier is namespaces and capabilities, which are kernel features that can themselves have bugs.

A VM has a hard hardware boundary. Even if an attacker fully compromises the guest kernel, they still face the hypervisor to reach the host. VM escapes exist but are rare and difficult.

Container threat model:
  attacker → container process → kernel exploit → host kernel ✓ (same kernel)

VM threat model:
  attacker → guest process → guest kernel exploit → hypervisor exploit → host kernel
                                                          (extra barrier)

This is why security-sensitive multi-tenant workloads (cloud providers running untrusted customer code) typically use VMs, not containers — or use purpose-built lightweight VMs like Firecracker or gVisor that combine VM-level isolation with container-like startup speed.


What Each is Good For

Use containers when:

  • You control the workload — your own app, your own team, trusted code
  • You want speed — fast startup, fast iteration, fast CI pipelines
  • You want density — many small services on one machine
  • You want consistency — same image from dev laptop to production

Use VMs when:

  • You need strong isolation — running untrusted or multi-tenant workloads
  • You need a different OS or kernel — Windows on Linux, or a specific kernel version
  • You need hardware-level guarantees — compliance, regulated environments
  • You need full OS features — a real init system, kernel modules, hardware drivers

In Practice, You Often Use Both

Most production cloud infrastructure layers VMs and containers together:

Physical host
  └── VM (hardware isolation boundary)
        └── Container runtime (Docker / containerd)
              ├── Container A
              ├── Container B
              └── Container C

The VM provides isolation between tenants or environments. Containers provide density, speed, and consistency within that VM. This is exactly how AWS ECS, Google Cloud Run, and most Kubernetes deployments work.


See Also