Skip to main content
RunBook Academy

Docker & ContainersI · FoundationsContainers vs VMs

Containers vs virtual machines

Foundation⏱ ~22 min

What you'll learn

  • Distinguish a container from a VM at every layer of the stack
  • Choose the right primitive for a given workload
  • Recognise where containers do and do not provide isolation

Prerequisites

None — start here.

Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-09

Not yet marked complete on this device.

The phrase “Docker is just a process” is the most useful sentence in this entire course. It is also the source of every misunderstanding sysadmins have about containers. A container is a Linux process, with a few extra kernel features wrapped around it. A VM is an entire hardware emulation. The two have almost nothing in common.

This lesson sets up the mental model that every later lesson assumes.

What a virtual machine actually is

A VM is hardware presented to a guest operating system. The host kernel exposes a virtual CPU, virtual RAM, virtual disk, and virtual NICs. The guest kernel boots inside that hardware, then runs userspace processes. To the guest, the world looks like real hardware — it doesn’t know it’s a VM.

A container, by contrast, is just a process tree running on the host kernel. The host kernel creates the illusion of isolation through namespaces (so the process sees its own PIDs, mounts, network, etc.) and cgroups (so the process is constrained in its use of CPU, RAM, and I/O). The container’s “operating system” is the host’s kernel.

flowchart TB
  subgraph VM["Virtual Machine"]
    direction TB
    AppA1[App A]
    AppA2[App B]
    LibA[libs / runtime]
    KernelA[Guest Kernel]
    HyperA[Hypervisor]
  end
  subgraph Container["Container"]
    direction TB
    AppB1[App A]
    AppB2[App B]
    LibB[libs / runtime]
    NoKernel[No kernel uses host kernel]
  end
  subgraph Host["Host kernel + hardware"]
    HostKernel[Host kernel]
    HW[CPU, RAM, disk, NIC]
  end
  VM --> HostKernel --> HW
  Container --> HostKernel --> HW

A side-by-side comparison

AspectVirtual machineContainer
Boot timeSeconds to minutesMilliseconds (just fork+exec)
Disk footprintGigabytes (full guest OS)Tens to hundreds of MB (app + libs)
RAM overheadHundreds of MB to GB (guest kernel)Single-digit MB (process overhead)
IsolationStrong (separate kernel)Weaker (shared kernel)
DensityTens per hostHundreds per host
Image portabilityHypervisor-bound; less portableOCI image; portable across distros
Kernel featuresWhatever the guest supportsWhatever the host supports

Where the boundary actually is

The kernel features that separate containers from “just a process” are:

  • Namespaces — give the process its own view of PIDs, mounts, the network stack, UTS (hostname), IPC, and (with user namespaces) users. Lesson “Linux namespaces” covers this in depth.
  • cgroups — limit CPU, memory, PIDs, block I/O, and (in cgroups v2) several other resources. The kernel enforces the limits; if the container exceeds its memory limit, the kernel OOM-kills it.
  • Filesystem isolation — the root filesystem is built from image layers (OverlayFS). Bind mounts and volumes punch holes through it.
  • Capability dropping — Linux capabilities are a fine-grained replacement for “root or not root”. A container can drop everything except CAP_NET_BIND_SERVICE, for example.
  • Seccomp — restricts which syscalls a process can make. The default Docker seccomp profile blocks ~50 dangerous syscalls.
  • AppArmor / SELinux — MAC profiles that further restrict what a process can do.

A container does not have its own kernel. A VM does. The choice is between “lots of processes, one kernel” (container) and “fewer guest kernels, each with their own kernel” (VM).

Portable image, non-portable kernel

The image is portable. The kernel it needs is not, and nothing in the image records that dependency. This is the practical edge of “shares the host kernel”, and it is where the container/VM distinction stops being an architecture-diagram fact and starts costing a night.

Read-only / Safethe container's OS is not the container's kernel
IMAGE=ubuntu:24.04
docker run --rm "$IMAGE" sh -c 'grep ^PRETTY_NAME /etc/os-release; uname -sr'
echo '--- host ---'
grep ^PRETTY_NAME /etc/os-release; uname -sr
PRETTY_NAME="Ubuntu 24.04.3 LTS"
Linux 5.14.0-427.el9.x86_64
--- host ---
PRETTY_NAME="Rocky Linux 9.4 (Blue Onyx)"
Linux 5.14.0-427.el9.x86_64

Illustrative output

The container reports Ubuntu 24.04 and a Rocky Linux kernel, because that is exactly what it is: Ubuntu’s userspace on the host’s kernel. The two uname lines being identical is the whole point of the exercise — there is no second kernel anywhere in this picture, and any check that concludes “the environment matches” from /etc/os-release alone has checked the half that was never in doubt.

This is also the test that disproves the “it’s a lightweight VM” model in one command, which is worth doing early with anyone who still holds it.

Read-only / Safepre-flight a host against a workload's kernel requirements
echo "kernel:           $(uname -r)"
echo "vm.max_map_count: $(sysctl -n vm.max_map_count)"
echo "cgroup version:   $(stat -fc %T /sys/fs/cgroup)"
echo "overlay module:   $(grep -c '^overlay ' /proc/modules)"
echo "unprivileged_userns: $(sysctl -n kernel.unprivileged_userns_clone 2>/dev/null || echo 'n/a on this kernel')"
kernel:           6.8.0-79-generic
vm.max_map_count: 262144
cgroup version:   cgroup2fs
overlay module:   1
unprivileged_userns: n/a on this kernel

Illustrative output

cgroup2fs confirms the unified hierarchy; tmpfs there means cgroup v1 and a different set of resource-limit behaviours. Capture this for every host class you run on and keep it next to the image requirements — that comparison is the thing a container image cannot do for you and a VM image would not have needed.

What a container is NOT

A container is not:

  • A lightweight VM. There is no guest kernel, no virtual hardware.
  • A sandbox by default. The default seccomp profile is a baseline; the default capability set is broad. You must harden.
  • A magic security boundary. A privileged container is roughly as powerful as root on the host.
  • An alternative to backups. Container filesystems are ephemeral by design. Persistent data lives in volumes or external storage.
  • An orchestration tool. Docker Compose describes multi-container apps; it does not schedule them across hosts. That requires Swarm, Kubernetes, or an external orchestrator.

When to choose what

  • Use containers when you want fast startup, high density, portable images, and CI/CD-driven deployment. Linux workloads, microservices, batch jobs.
  • Use VMs when you need a different kernel (Windows, BSD), a hard security boundary between workloads (multi-tenant), or per-workload kernel configuration that conflicts with the host.
  • Use both when you have a fleet of VMs as the host substrate and run containers inside them. This is the most common production pattern.
  • Use bare metal when neither container nor VM overhead is acceptable, or when the workload requires direct hardware access (GPU passthrough, DPDK, raw NVMe).

Common misconceptions

“Containers are more secure than VMs because they’re isolated.”

Containers share a kernel with the host. The kernel is the single largest attack surface. A kernel-level exploit inside one container affects every container on the host. VMs do not share a kernel.

“Containers start in milliseconds because they’re lighter.”

They start in milliseconds because there’s nothing to start. The init process is exec’d directly. There is no firmware, no bootloader, no kernel init, no userspace init system to skip. The “weight” being saved is the entire boot process.

“If I run my container with --memory 512m, the host is safe from OOM.”

The cgroup limits the container, but a misconfigured cgroup (or a cgroup-less OOM-killer bypass) can still cause issues. Plus, the host kernel’s OOM-killer chooses which process to kill based on oom_score, and your container’s processes may not get the lowest score. Always test OOM behaviour, don’t assume it.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Which of the following is true of a container but NOT of a virtual machine?

  2. Q2. A privileged Docker container running as root is roughly equivalent to root on the host.

  3. Q3. Name two Linux kernel features that together provide container isolation.

  4. Q4. A workload needs vm.max_map_count=262144. `docker run --sysctl vm.max_map_count=262144` is rejected by the daemon. Why, and what do you do?

  5. Q5. Running `uname -r` inside an ubuntu:24.04 container tells you which kernel that container is running.

Passing score: 75%. Answers are checked in this browser.

Where next

The next lesson, “The OCI ecosystem,” introduces the standards bodies that define the image and runtime formats. After that, “Docker architecture at a glance” walks through every daemon, runtime, and shim that participate in the lifecycle.