Containers vs virtual machines
Why a container is lighter than a VM.
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.)
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.
$ time docker run --rm alpine:3.20 echo "hello from a container"hello from a containerreal 0m0.407suser 0m0.021ssys 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.
$ docker image ls alpineREPOSITORY TAG IMAGE ID CREATED SIZEalpine 3.20 3c3e1a3a0b1e 3 weeks ago 8.17MB$ docker run -d --name idle alpine:3.20 sleep 3007f8e9d0a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e$ docker stats --no-stream idleCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS7f8e9d0a1b2c 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.
$ docker run hello-worlddocker: 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.
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.
docker run --rm alpine:3.20 uname -adocker run --rm alpine:3.20 cat /etc/os-release | head -n 5
Linux <id> 6.x.x-... #1 SMP ... x86_64 LinuxNAME="Alpine Linux"ID=alpineVERSION_ID=3.20.xPRETTY_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.