Docker vs VM
Containers and VMs both isolate apps on one server, but at different layers. Containers share the host kernel (fast, dense, thinner boundary); VMs virtualize hardware (slower, isolated, any OS). In the cloud, you run containers inside VMs.
A container and a virtual machine both run your app in its own isolated box on the same server. But they isolate at completely different layers — and that difference cascades into resource use, speed, security, and how you actually deploy them.
Mental model: A container is a process with a borrowed kernel; a VM is a whole computer in software.
What is a container, really?
A Docker container isn't a tiny computer. It's your application and its libraries running as isolated processes on the host machine — they share the host's kernel with everything else on that machine.
Linux namespaces create the illusion of isolation: each container sees its own filesystem, network stack, process table, and hostname. cgroups enforce resource limits: this container gets at most 512 MB of RAM, this one gets 2 CPUs. The kernel enforces both. The host OS kernel is still one, still shared.
The upshot:
- Image size: ~50 MB (just your app + libraries; no OS)
- Boot time: under one second (start a process, apply namespace/cgroup rules)
- Density: hundreds of containers on a single host
- Overhead: minimal (a few MB of RAM per container; microseconds of syscall overhead)
The cost:
- Thinner boundary: namespaces and cgroups are kernel-enforced, but a kernel exploit or a well-crafted container escape can potentially breach into the host or sibling containers
- Kernel lock-in: you cannot run a Windows kernel on a Linux host. On Mac or Windows laptops, Docker runs a hidden Linux VM behind the scenes so containers have a Linux kernel to share — you pay the VM tax invisibly
What is a virtual machine?
A virtual machine uses a hypervisor — software like KVM, ESXi, or Hyper-V — to simulate hardware. Each VM boots a complete guest operating system with its own kernel. From the guest OS's perspective, it owns the machine. The hypervisor intercepts hardware requests and routes them to real hardware or other VMs.
The upshot:
- Image size: gigabytes (a full OS + your app)
- Boot time: tens of seconds (the guest OS must initialize, load drivers, run services)
- Density: far fewer per host (a single high-end server might run 10–20 VMs comfortably)
- Isolation: a bug, exploit, or malicious code in one VM cannot access the hypervisor, host, or other VMs. The boundary is enforced in hardware and firmware
- OS flexibility: run Windows on a Linux hypervisor, or vice versa
Head-to-head comparison
| Aspect | Container (Docker) | Virtual Machine |
|---|---|---|
| Image size | ~50 MB | Gigabytes |
| Boot time | <1 second | Tens of seconds |
| Density (per host) | Hundreds | Tens |
| Kernel | Shared (host kernel) | Own kernel per VM |
| Isolation boundary | Namespaces + cgroups (kernel-enforced, thinner) | Hypervisor + hardware (stronger) |
| OS flexibility | Locked to host kernel | Any guest OS |
| Security model | Suitable for trusted multi-tenancy | Suitable for untrusted multi-tenancy |
The real-world answer: both, layered
You will hear engineers debate: "Should we use containers or VMs?" The honest answer is: you use both.
In every major cloud platform — AWS ECS, Google Kubernetes Engine (GKE), AWS Fargate — containers run inside VMs:
-
The VM is the trust boundary. Each customer or tenant gets their own VM (or a VM shared only with other instances from the same account). This is the hard isolation that prevents one customer's code from crashing another customer's workload.
-
Containers are the app boundary. Inside a VM, you run dozens or hundreds of containerized microservices. Containers give you fast startup, small images, and easy portability. If one container crashes, it doesn't bring down the whole VM — you just restart that container.
-
The payoff: You get the density and speed of containers, wrapped in the isolation and control of VMs.
When cloud providers say "serverless" (Fargate, Cloud Run), they're abstracting away the VM layer entirely, but it's still there — you've just delegated management to the cloud. The container still runs inside a VM; you just don't see it.
When to reach for each
Containers (Docker):
- Your app is portable, your environment is trusted, and you want fast iteration
- You're deploying on a fleet you control (your own Kubernetes cluster, or a managed service like EKS)
- You want to pack maximum density onto hardware you own or pay for by the hour
Virtual machines:
- You need strong isolation between untrusted workloads (multi-tenant SaaS, regulated compliance)
- You need to run a guest OS that differs from the host (Windows apps on a Linux datacenter)
- You're renting infrastructure where hypervisor isolation is the contract with your cloud provider
Both (containers inside VMs):
- You're building a production cloud system — this is the default in AWS, Google Cloud, and Azure
Why containers can't replace VMs
Containers are faster and denser, but they inherit the host kernel's behavior and bugs. If a kernel vulnerability exists, all containers on that host are exposed. VMs add a second layer: even if a guest OS is compromised, the hypervisor and other VMs are protected.
For public clouds serving multiple customers, this is non-negotiable. For internal infrastructure where you trust your developers and ops team, containers alone are often enough.
Verdict
Reach for containers when speed, density, and portability matter more than hard isolation. Reach for VMs when isolation is the primary requirement. In production, you'll almost certainly reach for both — containers running inside VMs — because they solve different problems at different scales.
Watch the 90-second reel to see this concept illustrated step by step.