All posts

Docker things that many people ignore

Docker the mental model

Nikhil Shinde/

Docker

I've been using Docker for a while now, and every day I discover something new that shifts my thinking and gives me better clarity on how it all fits together.

Quick Revision

Before diving deep, here's what you need to keep in mind as a foundation:

  • Docker shares the host kernel — unlike VMs, it doesn't spin up a whole new OS
  • Every instruction in a Dockerfile creates a cached layer, so Docker is smart enough to skip rebuilding layers that haven't changed
  • Docker images are templates, containers are the running instances of those templates
  • Images are immutable (frozen), containers are mutable (they can change while running)

Build Time vs Runtime

Docker has two distinct phases, and mixing them up causes a lot of confusion early on.

Build Time

This is when you run docker build. Docker reads your Dockerfile top to bottom, executes each instruction, and creates a snapshot (layer) after each one. If you rebuild and nothing changed on a particular line, Docker skips it and uses the cached layer — this is what makes rebuilds fast.

Commands you'll use at build time:

CommandWhat it does
FROMSets the base image
RUNExecutes a shell command (installs packages, compiles code)
COPY / ADDCopies files into the image
ARGDefines a variable available only during build
ENVSets environment variables (also available at runtime)
EXPOSEDocuments which port the app uses (informational only)
USERSets which user runs the process

Key distinction: ARG disappears after the build. ENV sticks around and is available when the container runs.

Runtime

This is when you run docker run. Docker takes your image (the blueprint) and spins up a live container from it — adding a writable layer on top of all the read-only image layers. This is where your application actually runs.

Commands that execute at runtime:

CommandWhat it does
CMDDefault command when the container starts
ENTRYPOINTThe main process of the container
VOLUMEDeclares a mount point for persistent data
HEALTHCHECKTells Docker how to test if the container is healthy

You can also interact with a running container using:

  • docker exec — run a command inside the container
  • docker attach — attach to the container's main process

How Docker Actually Works: Namespaces, cgroups, and chroot

This is the part most people skip, but it's what separates someone who uses Docker from someone who understands Docker.

Docker's power comes from three Linux kernel features working together. Individually, each one solves a piece of the puzzle. Together, they give you a container.

chroot — The Filesystem Boundary

If you've ever done an Arch Linux install, you've used chroot. It changes the root directory (/) for a process, so that process thinks a specific folder is the entire filesystem. It can't navigate above it.

chroot /myapp /bin/bash
# This process now sees /myapp as /
# It cannot access anything above /myapp

This gives us filesystem isolation — the process can't touch files it shouldn't. But that's all it does. The process can still see every other process on the host, share the network, and consume as much memory as it wants. The walls exist on the sides, but the ceiling and floor are wide open.

Namespaces — The Environment Boundary

Namespaces close the rest of the gaps. Each container gets its own set of namespaces, which means each container lives in its own little reality:

  • PID namespace — the container has its own process tree. A process with PID 1 inside the container is completely separate from PID 1 on the host.
  • Network namespace — the container has its own network interfaces and ports. Two containers can both listen on port 3000 without conflicting.
  • UTS namespace — the container has its own hostname.
  • Mount namespace — builds on chroot to fully isolate the filesystem view.
  • User namespace — the container can have its own user and group IDs.

With namespaces in place, containers are genuinely unaware of each other. They're isolated environments, not just isolated folders.

cgroups — The Resource Boundary

Control groups (cgroups) are the final piece. They don't deal with isolation — they deal with limits. cgroups let the kernel enforce how much of the host's resources any given container can consume:

  • CPU
  • Memory
  • Disk I/O
  • Network bandwidth

Without cgroups, a single runaway container could starve the entire host. With cgroups, you can say "this container gets 512MB of RAM and 0.5 CPU cores, and not a byte more."

Putting It All Together

┌─────────────────────────────────────┐
│  Container                          │
│                                     │
│  chroot     →  isolated filesystem  │
│  namespaces →  isolated environment │
│  cgroups    →  isolated resources   │
└─────────────────────────────────────┘

Think of it like building a room:

  • chroot builds the walls (filesystem boundary)
  • namespaces seal the ceiling and floor (environment boundary)
  • cgroups control the air supply (resource boundary)

Docker's job is to wire all three together automatically every time you run docker run. You never call these kernel primitives directly — Docker handles all of it under the hood.

This is also why containers are fundamentally different from VMs. A VM virtualizes the hardware and runs a completely separate OS kernel. A container shares the host kernel but uses these three primitives to simulate isolation. That's why containers start in milliseconds and VMs take minutes.


References

Nikhil Shinde