CoursesDocker for beginnersContainers vs virtual machines

Containers vs virtual machines

Why a container is lighter than a VM.

Beginner10 min · lesson 2 of 16
In plain terms
A virtual machine is a whole separate house with its own foundation, plumbing, and electricity. A container is an apartment in a shared building — it has its own locked door and rooms, but shares the building’s foundation and utilities (the host’s kernel). That sharing is why apartments are quicker to build and cheaper to run than houses.

A virtual machine, or VM, is a pretend computer running inside your real one. Some groundwork first. An operating system, or OS, is the base software a machine runs on: Windows, macOS, Linux. Buried inside every OS is its kernel, the part that talks straight to the hardware and hands out memory and processor time to your programs. Remember that word, kernel. The whole lesson hinges on it. Now the VM itself. A program called a hypervisor slices your real machine into pretend hardware: a pretend processor, a pretend disk, a pretend network card. A full operating system installs onto that pretend hardware and boots, exactly as it would on a real PC. So a VM carries its own kernel everywhere it goes, and it boots from cold, top to bottom, every single time. The result is a complete, sealed-off computer. Excellent at keeping things apart. Heavy and slow at everything else.

A container throws nearly all of that away. Here is the everyday version. A standalone restaurant down the street is the VM: its own building, its own kitchen, its own water and power hookups, expensive to open and slow to build. A stall in a shopping mall food court is the container. The counter, the till, and the menu are yours alone. The roof, the plumbing, the electricity? Already standing, shared by every stall on the floor. In Docker terms, that shared foundation is the host's kernel, and the host is whatever machine Docker is running on. A container never boots an operating system of its own. It borrows the kernel that is already running and carries only the files your app needs. (The packaged bundle of files has a name: an image. A running copy of that image is a container. The next lesson pulls those two apart properly.)

The two stacks, side by side
A VM stacks a whole operating system under every app. A container skips that and borrows the one kernel already running.

See it on your own machine

Talk is cheap. Two numbers end the argument: how fast a container starts, and how little it weighs. Alpine is a very small Linux image built for exactly this sort of job. The command below times a container that starts, prints one line, and quits. The --rm flag tells Docker to delete the container the moment it finishes, so nothing piles up on your disk. Run it twice. The first run has to download the image over the network; the second shows you the real speed.

terminal
$ time docker run --rm alpine:3.20 echo "hello from a container"
hello from a container
real 0m0.407s
user 0m0.021s
sys 0m0.017s

That 'real' line is wall-clock time, the actual time that passed between pressing Enter and getting your prompt back. Well under half a second. A virtual machine cannot get near that. Booting a VM means booting an entire operating system, which takes anywhere from twenty seconds to a couple of minutes, and it pays that bill on every single start. Now for weight. The -d flag runs the container in the background instead of tying up your terminal, and sleep 300 keeps it alive for five minutes so there is something to measure.

terminal
$ docker image ls alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine 3.20 3c3e1a3a0b1e 3 weeks ago 8.17MB
$ docker run -d --name idle alpine:3.20 sleep 300
7f8e9d0a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e
$ docker stats --no-stream idle
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
7f8e9d0a1b2c idle 0.00% 548KiB / 7.657GiB 0.01% 806B / 0B 0B / 0B 1

Roughly eight megabytes on disk, and about half a megabyte of memory while it sits there doing nothing. A modest VM image starts near a gigabyte and reserves gigabytes of memory before it does a scrap of useful work. On one host you might fit a handful of VMs, or hundreds of containers. That combination, tiny and instant, is why containers became the normal way to ship and run software, and why the bigger tools that spread apps across many machines all speak container.

When it won't start

Almost everyone hits this wall in their first hour, and it falls straight out of how containers work. Windows and macOS have no Linux kernel sitting there for a Linux container to borrow. So Docker Desktop quietly runs a small Linux VM in the background and keeps your containers inside it. The background service that actually runs them has a name: the Docker daemon. ('Daemon' is an old Unix word for a program that runs quietly in the background.) If that daemon, and the little VM it lives in, are not up yet, your very first command fails like this.

terminal
$ docker run hello-world
docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Run 'docker run --help' for more information.

Read the message plainly. The docker command you type is only a client, a messenger. It hands your request to the daemon, and the daemon does the actual work. Here the messenger could not reach it at all. On a Mac or Windows PC, open Docker Desktop and wait for the whale icon in the menu bar to stop animating and hold steady. On Linux the daemon runs directly on the machine, and you start it with sudo systemctl start docker. The wording looks alarming the first time you see it. Nine times out of ten it means the engine has not finished waking up.

The catch

Sharing a kernel is the trick. It is also the bill. Two virtual machines are held apart by a hardware boundary that the hypervisor enforces, a genuinely thick wall. Two containers are held apart by features built into the single kernel they share. Two of those features are called namespaces and control groups (cgroups for short); namespaces fence off what a container can see, cgroups ration what it can use, and you will meet both properly later. That divider is thinner than a VM's. It holds up fine for most workloads, but thinner is thinner, and that is the entire reason 'container hardening' exists as a topic. Everything a container does, it does by asking the shared host kernel to do it for it. Which means breaking out of a container comes down to getting at that kernel. Later lessons spend real effort on shrinking what a container is even allowed to ask for.

A container is not a tiny VM
New Docker users often expect to install any operating system inside a container, the way they would inside a VM. You cannot. A container has no kernel of its own. An Ubuntu image running on a Fedora host (Ubuntu and Fedora are both flavors of Linux) is Ubuntu's files sitting on Fedora's kernel, nothing more. The rule bites the other way too: Windows software will not run in a Linux container, and Linux software will not run on a Windows kernel. The two sides have to match. That mismatch is precisely why Docker Desktop hides a Linux VM on Mac and Windows in the first place.

Choosing between the two is not a contest of purity. Virtual machines still earn their keep when you need a kernel different from the host's, a hard wall between customers who do not trust each other, or isolation enforced by the hardware itself. Containers win when a pile of processes can happily share one kernel and you want them up in seconds. Most application work today lands in that second bucket, which is how Docker became the default way to package web services and background workers.

None of this is magic on Linux. The kernel hands a container its own private view of a few specific things: which process IDs it can see, which network stack it gets, which filesystem it believes sits at /, and which user IDs mean what. Those separate views are the namespaces. The cgroups then cap how much processor time and memory that group of processes may consume. Docker is the friendly toolkit wrapped around kernel machinery that was already there. On Docker Desktop for Mac or Windows, the small Linux VM underneath does the kernel duty, so your containers share that VM's kernel rather than your Windows or macOS one.

You will hear people say containers are insecure because they share the kernel. They are naming a genuine trade-off, not scoring a point that sinks the model. A shared kernel means one kernel bug can reach every container on the host. So the defense moves elsewhere: give the process inside the container the least privilege it can work with, keep the host patched, and never hand a container the powers that let it climb out. The advanced courses take those apart one at a time. For now hold the middle position: lighter than a VM, far better fenced than a loose process on the host, still leaning on one shared kernel.

A quick way to decide. Do you need Windows containers on Windows Server, or a full guest operating system for a legacy appliance that expects to own the machine? That is VM territory, or a specialized runtime. Do you need twenty copies of the same Linux Node or Python service? Containers, without hesitation. Plenty of companies run both side by side, and that is perfectly normal. The pain comes from putting a workload in the wrong one.

Try this

Start a tiny Alpine container and see for yourself how little it weighs next to any VM you have ever launched. Then look at the kernel from inside the container and notice whose it is.

terminal
docker run --rm alpine:3.20 uname -a
docker run --rm alpine:3.20 cat /etc/os-release | head -n 5
output
Linux <id> 6.x.x-... #1 SMP ... x86_64 Linux
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.x
PRETTY_NAME="Alpine Linux v3.20"
HOME_URL="https://alpinelinux.org/"

Takeaway

The kernel line tells you everything. A container borrows the host's kernel and fences off a process inside it; a VM boots a whole guest operating system of its own. That one difference explains the sub-second start, the eight-megabyte image, and the fact that a single kernel bug is everyone's problem at once.

Quick check
01An Alpine Linux container starts on your Ubuntu host in under a second. Whose kernel is it running on?
Incorrect — No. Booting a kernel costs seconds and a chunk of memory, and containers never do it. That is the VM route.
Correct — Yes. The container is Alpine's filesystem running on the kernel that was already up, which is why it started instantly.
Incorrect — A hypervisor fakes hardware for virtual machines. No hypervisor is involved here at all.
Incorrect — Docker adds no kernel per container. Sharing the one already running is the whole point.
02The lesson called the divider between two containers thinner than the one between two virtual machines. What actually keeps two containers apart?
Correct — Namespaces fence off what each container can see and cgroups ration what it can use, all inside the one kernel they share.
Incorrect — No. A hypervisor separates virtual machines. Containers never involve one.
Incorrect — No. The separation comes from kernel features rationing shared resources, not from private hardware.
Incorrect — No. Containers have no kernel of their own, which is exactly why the divider is thinner than a VM's.
03You try to run an image full of Windows-only software on your Linux host. Given how containers work, what should you expect?
Incorrect — No. Docker translates nothing. The software inside calls the host kernel directly.
Incorrect — No. A Linux container emulates no other operating system. It borrows the host's Linux kernel as it is.
Incorrect — No. A container never carries a kernel of its own, so there is nowhere to put one.
Correct — The container and the host kernel have to be the same family, so Windows software needs a Windows kernel underneath it.

Related