Containers vs Virtual Machines: What's Actually Different Under the Hood

Containers vs Virtual Machines: What's Actually Different Under the Hood

"Containers are lightweight, VMs are heavy" is the line everyone repeats. It's true, but repeated without explanation it's just trivia. The actual reason comes down to one architectural decision: what layer of the system gets duplicated, and what gets shared.

A virtual machine duplicates the whole computer

A hypervisor (like VMware ESXi, KVM, or Hyper-V) sits directly on physical hardware — or on a host OS — and creates virtual hardware: virtual CPUs, virtual disks, virtual network cards. Each virtual machine boots its own complete, independent operating system on top of that virtual hardware, kernel included.

That's why a VM boots slowly (it's running a full OS boot sequence) and why a single host running ten VMs is really running ten separate kernels, ten separate copies of core OS services, and ten separate chunks of memory reserved for all of that overhead — even if the ten VMs are all running the exact same Linux distribution.

A container shares the host's kernel

Containers (Docker, containerd, Podman) don't virtualize hardware or boot a separate OS at all. They run as regular processes on the host's existing kernel — the same kernel the host OS itself uses. What makes a container feel like an isolated machine is two Linux kernel features working together:

  • Namespaces — give a process its own isolated view of specific resources: its own process ID space (so it sees itself as PID 1), its own network stack, its own filesystem mount points, its own hostname. The process believes it's alone on the machine even though the kernel underneath is shared with everything else running on the host.
  • Cgroups (control groups) — cap and account for how much CPU, memory, and I/O each process (or group of processes) is allowed to consume, so one container can't starve the others.

No second kernel boots. No virtual hardware gets emulated. A container is, fundamentally, an ordinary process with a very convincing set of blinders on.

Why that makes containers so fast

Starting a container means: create some namespaces, apply some cgroup limits, and run a process. That's a kernel-level operation measured in milliseconds. Starting a VM means: boot an entire operating system from scratch, which is measured in seconds to minutes.

Container images are also built from stacked, read-only layers using a union filesystem (like overlayfs) — each layer represents one step in the Dockerfile, and identical layers get reused and cached across images instead of duplicated. That's why pulling a new image that shares a base with one you already have is often nearly instant, and why images can be so much smaller than a full VM disk image, which has to contain an entire OS filesystem.

The security trade-off nobody skips explaining

Sharing a kernel is the whole reason containers are fast, and it's also their fundamental limitation as an isolation boundary. All containers on a host ultimately depend on the security of that one shared kernel. A serious kernel vulnerability is a potential path from inside one container to the host, or to other containers on it — a category of risk that essentially doesn't exist for VMs, where each guest has its own separate kernel and the hypervisor's job is specifically to keep those boundaries hard.

This is why multi-tenant environments running genuinely untrusted workloads (a cloud provider running arbitrary customer code, for instance) generally don't rely on container isolation alone. Common approaches include running containers inside lightweight VMs (AWS Firecracker, used for Lambda and Fargate), sandboxed runtimes that intercept syscalls before they reach the real kernel (Google's gVisor), or Kata Containers, which gives each container its own minimal VM. All of them are, in effect, buying back some of the VM's isolation guarantee while trying to keep container-like speed.

So which one do you actually want?

  • Containers — microservices, CI/CD pipelines, local dev environments, anywhere you're packaging one application and want fast startup, high density, and easy versioning.
  • VMs — running a genuinely different OS (Windows workloads on a Linux host, for example), strict security/compliance isolation between tenants, or any workload where "a kernel exploit in one tenant reaches another" is not an acceptable risk.

In practice, most production cloud environments use both at once: containers for density and deployment speed, running inside VMs for the hard security boundary — defense in depth rather than picking one over the other.

Frequently Asked Questions

Do containers have their own IP address? They can — container network namespaces give each container its own virtual network interface and IP on a virtual bridge network, even though the underlying physical network interface is shared with the host.

Can a container run a different OS than the host? Not really, since it shares the host kernel — a Linux host can't natively run Windows containers, and vice versa, without an intermediate VM layer. (Docker Desktop on Windows/Mac quietly runs a lightweight Linux VM in the background specifically to make Linux containers work.)

Are containers less secure than VMs by default? They have a larger shared attack surface (one kernel for everything), but with proper configuration — dropped capabilities, read-only filesystems, non-root users, seccomp profiles — well-hardened containers are secure enough for the vast majority of real-world use cases. The gap matters most for hostile multi-tenant workloads.

Comments (0)

Leave a Reply

Log in to post a comment.