Containers vs virtual machines
Why a container is lighter than a VM.
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.
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 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.
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.
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.