Docker vs Virtual Machines
How containers and VMs really differ. Shared kernel vs hypervisor, startup time and density, isolation strength, when to use each, and why both together is normal.
Both let you run isolated workloads on shared hardware. The difference is what gets virtualised, and everything else follows from it.
Each app needs its own complete OS copy. More apps = more wasted memory.
All apps share one OS. No duplication - containers only carry your app and its dependencies.
The architectural difference
A virtual machine virtualises hardware. A hypervisor presents virtual CPUs, memory and disks, and each VM runs a complete guest operating system with its own kernel.
A container virtualises the operating system. It is a process on the host, isolated using kernel features, namespaces for what it can see, cgroups for what it can use, and it shares the host kernel.
Virtual machines Containers
┌─────────┬─────────┐ ┌─────────┬─────────┐
│ App A │ App B │ │ App A │ App B │
│ Libs │ Libs │ │ Libs │ Libs │
│ Guest OS│ Guest OS│ ← own kernel └─────────┴─────────┘
├─────────┴─────────┤ ├───────────────────┤
│ Hypervisor │ │ Container runtime│
├───────────────────┤ ├───────────────────┤
│ Host OS │ │ Host OS │ ← one kernel
├───────────────────┤ ├───────────────────┤
│ Hardware │ │ Hardware │
└───────────────────┘ └───────────────────┘
A container has no guest OS to boot, no kernel to initialise, and no duplicated system libraries. That single fact produces every practical difference below.
The practical comparison
| Container | Virtual machine | |
|---|---|---|
| Startup | Milliseconds | Tens of seconds |
| Size | Megabytes | Gigabytes |
| Density per host | Hundreds | Tens |
| Memory overhead | Near zero | A full OS each |
| Kernel | Shared with host | Its own |
| Run a different OS kernel | No | Yes |
| Isolation strength | Good | Stronger |
| Snapshot and live migrate | Limited | Mature |
The two limits that actually matter
A container cannot run a different kernel. Linux containers need a Linux kernel. This is why Docker Desktop on macOS and Windows runs a hidden Linux VM: your containers are not native, and that is the reason bind-mounted file access is noticeably slow on those platforms. Windows containers exist but require a Windows host.
Isolation is weaker than a VM's. Containers share one kernel, so a kernel vulnerability is a shared attack surface, and a container escape reaches the host. A VM escape has to get through the hypervisor, which is a far smaller and more hardened boundary.
For your own code that distinction rarely matters. For genuinely untrusted multi-tenant workloads it does, which is why platforms running arbitrary customer code use VMs, or microVM runtimes like Firecracker and gVisor that combine container ergonomics with a stronger boundary.
Are containers less secure?
Not inherently, but the boundary is thinner, and the defaults are more permissive than people assume.
The practical risks are usually configuration rather than architecture:
- Containers run as root by default unless you set
USER. --privilegedeffectively grants host-level access.- Mounting
/var/run/docker.sockgives control of the daemon, which is equivalent to root on the host. - A vulnerable base image ships whatever it ships.
Set a non-root USER, drop capabilities, use a read-only root filesystem, and scan images. With those, containers are secure enough for the overwhelming majority of workloads.
When to use which
Containers for stateless applications and microservices, CI build and test isolation, anything needing fast scaling, packaging an application with its dependencies, and local development environments.
Virtual machines when you need a different operating system or kernel, when running genuinely untrusted third-party code, for legacy applications expecting a full OS, where compliance requires hardware-level isolation, and for kernel-level work such as drivers or custom modules.
In practice you use both
The framing "containers versus VMs" is mostly a false choice. Nearly every cloud deployment runs containers inside VMs:
- An EKS or GKE node is a VM running dozens of pods.
- ECS on EC2 is containers on VMs.
- Fargate runs each task in its own microVM.
You get container density and packaging within a VM boundary between tenants. The hypervisor isolates customers from each other; containers pack workloads efficiently inside that boundary.
That is also why "containers replaced VMs" is wrong. Containers changed how applications are packaged and scheduled; VMs remain the unit of hardware isolation underneath.
Quick reference
| Question | Answer |
|---|---|
| Faster to start? | Container (milliseconds vs tens of seconds) |
| Smaller? | Container (MB vs GB) |
| Stronger isolation? | VM |
| Run a different kernel? | VM only |
| Higher density? | Container |
| Better for untrusted code? | VM, or a microVM runtime |
| What runs on cloud Kubernetes? | Containers, inside VMs |
Key takeaways
- VMs virtualise hardware and run their own kernel; containers virtualise the OS and share the host kernel.
- Every practical difference follows from the shared kernel: startup time, size, density, isolation.
- A container cannot run a different kernel, which is why Docker on macOS and Windows uses a hidden Linux VM.
- That hidden VM is why bind mounts are slow on those platforms.
- Container isolation is good but thinner than a VM's. Untrusted multi-tenant code deserves a VM or microVM.
- Most container security problems are configuration: root by default,
--privileged, a mounted Docker socket. - The two are complementary. Nearly all cloud container platforms run containers inside VMs.
Check your understanding
Running Linux containers on a Mac
Docker Desktop on macOS runs Linux containers. How, given macOS does not have a Linux kernel?
Running untrusted code
A platform runs arbitrary code submitted by customers. Why would containers alone be a questionable isolation boundary?
Frequently Asked Questions
What is the main difference between Docker and a virtual machine?
What gets virtualised. A VM virtualises hardware. A hypervisor presents virtual CPUs and disks, and each VM boots a complete guest operating system with its own kernel. A container virtualises the operating system. It is a process on the host isolated by kernel namespaces and cgroups, sharing the host kernel. That is why containers start in milliseconds and measure in megabytes while VMs take tens of seconds and measure in gigabytes.
Are containers more secure than virtual machines?
No. VMs provide stronger isolation. All containers on a host share one kernel, so a kernel vulnerability or a container escape can reach the host and other containers, whereas a VM escape must also get past the hypervisor, a far smaller and more hardened boundary. For your own code the difference rarely matters in practice. For genuinely untrusted multi-tenant code it does, which is why such platforms use VMs or microVM runtimes like Firecracker and gVisor.
Can Docker run Windows containers on Linux, or Linux containers on Windows?
Not natively, because containers share the host kernel. Windows containers need a Windows kernel and Linux containers need a Linux one. Docker Desktop makes Linux containers work on macOS and Windows by running a lightweight Linux VM behind the scenes, on Windows it typically uses WSL 2. Everything behaves transparently, but it explains why bind-mounted file access is noticeably slower on those platforms than on native Linux.
Do containers replace virtual machines?
No, and in practice they are used together. Almost every cloud container platform runs containers inside VMs: an EKS or GKE node is a VM hosting many pods, and Fargate runs each task in its own microVM. You get container packaging and density within a VM boundary that separates tenants. Containers changed how applications are packaged and scheduled; VMs remain the unit of hardware-level isolation underneath.
When should I use a VM instead of a container?
When you need a different operating system or kernel version than the host, when running genuinely untrusted third-party code, for legacy applications that expect a full operating system with init and multiple services, when compliance requires hardware-level isolation between workloads, and for kernel-level work such as drivers or custom modules. For everything else, stateless services, CI jobs, development environments, containers are lighter and faster.
Why is Docker slow on macOS and Windows?
Because Linux containers cannot run natively there. Docker Desktop runs a Linux VM and your containers live inside it, so every bind-mounted file access crosses the boundary between the host filesystem and the VM. For a project with many small files, such as a large node_modules, this is the dominant cost. Mitigate it by bind-mounting only the source directory, keeping dependency directories in named volumes, and enabling VirtioFS in Docker Desktop's settings.