Containers vs Virtual Machines

Docker & DevOps · Lesson 2 of 26 8 min read Updated

Containers and virtual machines both let you run an app in its own isolated space. They look similar from the outside, so beginners often treat them as the same thing. They are built very differently, and that difference decides how fast they start, how much memory they use and how safe they are.

In this lesson you will compare the two, see the Linux features that make containers possible, and understand what Docker actually runs on your Windows or Mac laptop.

Virtual machines: a whole computer in software

A virtual machine (VM) pretends to be a complete computer. A program called a hypervisor (VirtualBox, VMware, Hyper-V or KVM) creates fake hardware: a CPU, memory, a disk and a network card. On that fake hardware you install a full guest operating system, with its own kernel, its own drivers and its own system services. Your app runs on top of that.

The kernel is the core of an operating system. It talks to the hardware, manages memory and decides which program uses the CPU next. Every VM has its own kernel, which is why you can run Windows inside a VM on a Linux server.

This gives very strong isolation, but it costs a lot. A VM for a small web app might need 1 GB of memory just for the guest operating system, several gigabytes of disk and 30 seconds or more to boot.

Containers: isolated processes on a shared kernel

A container does not pretend to be a computer. It is an ordinary process (or a small group of processes) running on the host's kernel. The kernel simply gives it a restricted view of the system. Two Linux kernel features do the work:

  • Namespaces control what a process can see. With its own namespaces, a container sees only its own processes, its own network interfaces, its own host name and its own file system.
  • Control groups (cgroups) control what a process can use: how much memory, how much CPU time and how many processes.

Because there is no guest kernel to boot, a container starts in well under a second and uses only the memory your app needs. A laptop with 8 GB of memory can run a few VMs but dozens of containers.

Virtual machineContainer
What is isolatedA whole computer with its own kernelA process with its own view of the system
Start-up timeTens of secondsUsually under a second
Size on diskGigabytesMegabytes to a few hundred megabytes
Memory overheadA full guest OS for each VMAlmost none beyond the app
Can run a different OS kernelYes (Windows on Linux, for example)No, it uses the host kernel
Isolation strengthVery strongStrong, but the kernel is shared

Seeing the shared kernel for yourself

You can prove that a container uses the host's kernel. On a Linux server, compare the kernel version on the host with the one inside a container. The --rm option deletes the container as soon as the command finishes, and alpine:3.22 is a tiny Linux distribution image.

Terminal
uname -r
docker run --rm alpine:3.22 uname -r
Output
6.8.0-87-generic
6.8.0-87-generic

The same kernel answers both times. Now ask which Linux distribution each one thinks it is:

Terminal
grep PRETTY_NAME /etc/os-release
docker run --rm alpine:3.22 grep PRETTY_NAME /etc/os-release
Output
PRETTY_NAME="Ubuntu 24.04.3 LTS"
PRETTY_NAME="Alpine Linux v3.22"

The host is Ubuntu, but the container has Alpine's files, Alpine's package manager and Alpine's tools. A container image brings its own user space (files and programs) but borrows the host's kernel. That is why a container built on Alpine runs happily on an Ubuntu, Debian or Fedora server.

The process namespace is just as easy to see. Inside a container, your command is process number 1 and there is nothing else:

Terminal
docker run --rm alpine:3.22 ps
Output
PID   USER     TIME  COMMAND
    1 root      0:00 ps

On the host, ps would list hundreds of processes. The container cannot see any of them.

Limiting resources with cgroups

Control groups stop one container from eating all the memory or CPU on a server. You set limits when you start the container:

Terminal
docker run -d --name report-job --memory 256m --cpus 0.5 alpine:3.22 sleep 600

This container may use at most 256 MB of memory and half of one CPU core. If the app inside tries to use more memory, the kernel stops it (you will see exit code 137 in Debugging & Troubleshooting Containers). Without limits, a container may use everything the host has, which is the default.

What runs on Windows and macOS?

Containers need a Linux kernel, but Windows and macOS do not have one. So Docker Desktop quietly runs a small, fast Linux virtual machine and runs your containers inside it:

  • On Windows, Docker Desktop uses WSL 2 (Windows Subsystem for Linux), which runs a real Linux kernel in a lightweight VM managed by Windows.
  • On macOS, Docker Desktop uses Apple's virtualization framework to run its Linux VM.
  • On a Linux server you install Docker Engine directly, with no VM at all. This is what production servers use.

You do not have to manage this VM yourself. The docker command on your laptop talks to the Docker Engine inside it, and published ports such as localhost:8080 are forwarded for you.

When to choose which

Containers and VMs are not enemies. Most cloud servers are themselves VMs, and you run containers inside them.

  • Use containers to package and run apps: web servers, APIs, databases for development, background workers and build tools.
  • Use VMs when you need a different kernel or operating system, very strong isolation between customers, or a full desktop environment to test something.

Common mistakes

  • Expecting a container to boot like a VM. A container has no boot process and usually no system services such as systemd. It runs one main program; when that program exits, the container stops.
  • Trying to run a Windows app in a Linux container. The container uses the host's Linux kernel, so it can only run Linux programs.
  • Forgetting resource limits on shared servers. One runaway container without --memory can slow every other app on the machine.

Key points

  • A VM emulates a whole computer and runs its own guest kernel; a container is an isolated process on the host's kernel.
  • Linux namespaces limit what a container can see; cgroups limit what it can use.
  • Containers start faster and use far less memory and disk than VMs.
  • An image brings its own files (user space) but shares the host kernel, so an Alpine container runs on an Ubuntu host.
  • Docker Desktop on Windows and macOS runs a small Linux VM; on Linux servers Docker Engine runs directly.

Exercise

On a computer with Docker, run a container from alpine:3.22 with a limit of 128 MB of memory that prints its host name with the hostname command. Then run the same command without Docker on your own machine and compare the two names. Explain in one sentence why they are different.

Show solution
Terminal
docker run --rm --memory 128m alpine:3.22 hostname
hostname
Output
5d1e8a0c9b27
aarav-laptop

The container has its own UTS namespace (the namespace for the host name), so Docker gives it a name based on its container ID instead of your computer's name. Your container ID will be different.

Quick Quiz

1. Which Linux feature limits how much memory a container can use?

2. Why can an Alpine Linux container run on an Ubuntu server?

3. How does Docker Desktop run Linux containers on Windows?

4. Which statement about VMs is true?

Learn Docker & DevOps with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call