An image is a stack of layers
Each instruction in a Dockerfile that changes files makes one read-only layer, named by a hash of its content. Two images that share a base share those bytes on disk and over the network.
No virtual machine boots. Docker downloads the layers it's missing, stacks them into a filesystem, and asks the Linux kernel to start one ordinary process that can only see what it's been given.
All timings on this page are illustrative. They show where the time goes, not what your machine will do.
Nothing is on this machine yet. Before anything can start, the daemon downloads every layer of the image. Then starting the container itself is quick.
Ready. Run the image and follow the command through each part.
| layer | made by | size | in |
|---|---|---|---|
| a1f3 | FROM node:22-slim | 74 MB | 1.0 1.1 1.2 |
| b7c2 | RUN npm ci | 41 MB | 1.0 1.1 |
| c9e0 | COPY . . | 2 MB | 1.0 |
| c4d8 | COPY . . | 2 MB | 1.1 |
| e5a1 | RUN npm ci | 46 MB | 1.2 |
| c2b6 | COPY . . | 2 MB | 1.2 |
(no containers)
Each instruction in a Dockerfile that changes files makes one read-only layer, named by a hash of its content. Two images that share a base share those bytes on disk and over the network.
Namespaces give it a private view of process IDs, mounts and the network. A cgroup caps its memory and CPU. Underneath, it's one more process on the host's kernel.
Each container gets a thin writable layer on top of the image. Whatever it writes there disappears with it. Data that must outlive a container goes in a volume.
Live: the line that made this layerDockerfile
FROM node:22-slimWORKDIR /appCOPY package*.json ./RUN npm ci --omit=devCOPY . .CMD ["node", "server.js"]Live: the terminal
(nothing run yet) $ docker ps -a CONTAINER IMAGE STATUS HOST PID MEM
Read the Dockerfile from the top. FROM brings in someone else's layers, here a slim Debian with Node installed. Each RUN and COPY adds one more.
The order is deliberate. Dependencies change rarely and weigh a lot, so package.json is copied and installed first. Application code changes on every commit and weighs little, so it goes last. A change invalidates its own layer and every one above it, never the ones below.
The host PID column shows the other half. Inside, the server is PID 1 and sees no other processes. Run ps on the host and it's there like any other program. Containers isolate processes. They don't virtualise hardware.
Go over a cgroup's memory limit and the kernel's OOM killer sends SIGKILL. There's no exception to catch and no log line from your app. 137 means 128 + signal 9.
Removing and re-creating a container is routine: upgrades do it. Anything in the writable layer goes with it. Restarting keeps it, which is why the bug can hide for weeks.
Usually a Dockerfile copying the code before installing dependencies, or a base image tag that moves. Both make layers that could be shared look new.
A server already runs web:1.0. You change one line of server.js, build web:1.1 and run it there. How much does the server download?
Now the Dockerfile is reordered: COPY . . comes before RUN npm ci. Every code change still edits only server.js. What happens to the npm ci layer?
Every image here already exists in the registry. docker build runs each instruction in a temporary container and snapshots the result as a layer, using the same rule for its cache: a layer is reused only if its instruction and everything below it are unchanged.
Layers are compressed tarballs, fetched several at a time, and the ids here stand in for full sha256 digests. Unpacked sizes on disk are larger than what crosses the network.
Containers also get a network namespace with its own interfaces, usually bridged to the host. Real isolation also leans on dropped capabilities, seccomp filters and user namespaces, none of which are modelled.
Containers need a Linux kernel, so Docker Desktop runs one in a small virtual machine and your containers run inside it. On a Linux server there's no VM at all, which is what this page shows.