A virtual machine and a container both run an application in isolation. They isolate at different levels.
Virtual machines Containers
+------------+ +------------+ +------------+ +------------+
| app A | | app B | | app A | | app B |
| libs, bins | | libs, bins | | libs, bins | | libs, bins |
| guest OS | | guest OS | +------------+ +------------+
| + kernel | | + kernel | | container runtime |
+------------+ +------------+ +-----------------------------+
| hypervisor | | host OS + host kernel |
+----------------------------+ +-----------------------------+
| hardware | | hardware |
+----------------------------+ +-----------------------------+- A VM gets virtual hardware from a hypervisor and boots its own kernel.
- A container is a normal Linux process on the host kernel. The kernel restricts what it can see and use.
Side by side
| Virtual machine | Container | |
|---|---|---|
| Kernel | Its own guest kernel | The host's kernel |
| Start time | Tens of seconds (boots an OS) | Milliseconds to a second (starts a process) |
| Typical size | Gigabytes | Megabytes (alpine is 7.8MB) |
| Isolation boundary | Hypervisor | Kernel namespaces and cgroups |
| Runs a different OS family | Yes (Windows guest on Linux host) | No, a Linux image needs a Linux kernel |
| Unit of work | A whole machine | One process and its children |
Neither replaces the other. Cloud hosts run containers inside VMs: the VM isolates tenants, the containers package applications.
What makes a process a container
The Linux kernel provides two features that Docker combines:
| Feature | Controls | Example |
|---|---|---|
| What the process can see | its own filesystem root, process list, hostname, network interfaces | |
| How much it can use | 512MB of memory, 1 CPU |
Inside its PID namespace, the container's main process is PID 1. When that process exits, the container stops.
See it on the host
The kernel is shared. The host and an Alpine container report the same release:
uname -r
docker run --rm alpine:3.20 uname -r6.8.0-45-generic
6.8.0-45-genericThe filesystem is not shared. Each side reads its own /etc/os-release:
grep PRETTY /etc/os-release
docker run --rm alpine:3.20 grep PRETTY /etc/os-releasePRETTY_NAME="Ubuntu 24.04.1 LTS"
PRETTY_NAME="Alpine Linux v3.20"The hostname comes from a UTS namespace. Docker sets it to the container's short ID:
hostname
docker run --rm alpine:3.20 hostnamepier
99fddfcd7640The output crosses the boundary
A container's standard output streams back to your terminal, so normal shell redirection on the host works:
docker run --rm alpine:3.20 cat /etc/os-release > ~/alpine-os-release.txtThe file is written by your host shell, from text the container printed.