Handbooks  /  Docker
Engineering~12 min readBeginner-friendly
Deep Dive

Docker: a container is a lie told by the kernel.

"Works on my machine" died because we learned to ship the machine. But the machine we ship isn't a virtual computer — it's an ordinary process wearing an elaborate disguise, and the disguise is maintained by the host kernel. Understand that one trick and images, layers, volumes and networking all fall into place.

01

Not a VM: namespaces + cgroups

A virtual machine virtualizes hardware and boots its own kernel — heavyweight by construction. A container virtualizes nothing. It's a normal Linux process (or process tree) that the kernel systematically deceives via two mechanisms:

The two kernel features that ARE containers
NamespacesLimit what the process can SEE: its own process table (it thinks it's PID 1), network stack, mount table, hostname, users. Six lies, one per namespace.
cgroupsLimit what it can USE: CPU shares, memory ceilings, IO bandwidth. The reason one greedy container can't starve the host.

That's why containers start in milliseconds (nothing boots — a process forks), share the host's memory efficiently, and can be packed by the hundred onto one box. It's also why Linux containers need a VM shim on Mac/Windows — the lies are Linux-kernel lies. Docker's real invention wasn't the container; the kernel had the pieces. It was the packaging — which brings us to images.

02

Images: a filesystem in layers

An image is the filesystem the lie will present — built as a stack of read-only layers, one per Dockerfile instruction, each recording only the changes it made. Layers are content-addressed (Git vibes, deliberately): ten images built on the same Ubuntu base share those bytes once, on disk and in the registry. When a container runs, a thin writable layer goes on top; the image below stays immutable, which is why a container is disposable by design — kill it and the writable layer is all that dies.

→ The rule that halves your build times

The build cache reuses layers top-down until the first instruction that changed — everything after rebuilds. So order Dockerfiles by volatility: dependencies first (change rarely), source code last (changes constantly). COPY package.json → RUN npm install → COPY . . exists precisely so editing your code doesn't reinstall your dependencies.

03

Dockerfiles that ship small

The classic sins: bloated images carrying compilers into production, root processes, latest-tag roulette. The fixes are few and mechanical:

PracticeWhy
Multi-stage buildsbuild in a fat stage, COPY --from=build only the artifact into a slim final stage — GBs → tens of MBs
Small bases-slim / distroless / alpine (mind musl quirks) — less surface, faster pulls
Pin versionsFROM node:22.4, never :latest — builds you can reproduce next year
Non-root USERcontainer escape from root is host trouble; from uid 1000 it's a shrug
.dockerignorekeep node_modules, .git and secrets out of the build context entirely
One process per containerthe unit of scaling, logging and restart — compose processes, don't bundle them
04

State: volumes, and the amnesia feature

Containers forget by design — the writable layer dies with them. That amnesia is a feature (identical fresh state every run) until you're running a database. The escape hatches: volumes (Docker-managed directories mounted into the container — survive restarts, the production answer) and bind mounts (a host directory mapped straight in — the dev-loop answer, your editor writing into the running container). The discipline is knowing which data is cattle (rebuildable — leave it in the container) and which is sacred (the database files — volume, backed up).

05

Networking: who can talk to whom

Each container gets its own network namespace — its own localhost, which is the #1 beginner trap: localhost inside a container is the container, not your machine. Containers on the same user-defined bridge network reach each other by name (Docker runs the DNS: db:5432 just works); the outside world reaches a container only through explicitly published ports (-p 8080:80 — host:container). That's 90% of Docker networking: name-based reach within a network, port-mapping at the edge, and nothing visible unless you published it.

06

Compose, and the handoff to Kubernetes

Real apps are several containers — API, database, cache, worker. Compose is the one-machine orchestrator: a YAML file declaring services, networks and volumes, brought up with one command, networked by name automatically. It is the standard dev environment and a perfectly honest small-deployment tool.

What Compose doesn't do is fleet: many machines, self-healing, rolling deploys, autoscaling. That's Kubernetes — same containers, a much bigger brain around them. The pipeline in practice: Dockerfile → image → registry → Compose (dev) → Kubernetes (prod), with the image as the invariant artifact moving through it. Master the image and the rest is configuration.

Frequently asked

Quick answers

Container vs VM?

A VM boots its own kernel on virtual hardware. A container is host-kernel processes inside namespaces (what it sees) + cgroups (what it uses). Milliseconds vs minutes.

How do layers work?

One read-only layer per Dockerfile instruction, content-addressed and shared across images; a writable layer on top at runtime. Cache reuses layers until the first changed instruction.

What is a multi-stage build?

Build with the fat toolchain in stage one, copy only the artifact into a minimal final stage — small, clean production images.

Compose vs Kubernetes?

Compose orchestrates one machine (dev, small deploys). Kubernetes orchestrates fleets: self-healing, rolling deploys, autoscaling.

Docker · Engineering · Vibe Engines · 2026
Finished this one? 0 / 49 Handbooks done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.