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 VM boots a guest kernel. A container is a process that shares the host kernel and gets its own filesystem, network, and process view.

A virtual machine (VM) is a computer emulated on your real one. An operating system (Windows, macOS, Linux) includes a kernel, the part that talks to hardware and schedules processes. A hypervisor presents fake CPU, disk, and network hardware; a full guest OS boots on that hardware, with its own kernel, every time. Isolation is strong. Startup and resource use are heavy.

A container skips the guest OS. It is a process on the host, isolated with its own filesystem, network, and process list, but it uses the host kernel. It never boots an operating system of its own. The packaged filesystem is an image; a running copy of that image is a container. The next lesson separates those two.

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 trade-off. Hypervisors isolate VMs at a hardware boundary. Containers are isolated by kernel features: namespaces (what the process can see) and cgroups (what it can consume). That boundary is thinner than a VM's, which is why container hardening exists. A process in a container still makes syscalls to the shared host kernel; a breakout is about reaching that kernel. Later courses shrink what the process is 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.

On Linux this is kernel machinery, not a Docker invention. Namespaces give a process its own view of PIDs, network, mount, and user IDs. Cgroups cap CPU and memory. Docker is a toolkit around that. On Docker Desktop for Mac or Windows, a small Linux VM supplies the kernel, so containers share that VM's kernel, not Windows or macOS.

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