Container Internals Deep Dive 00
Kickoff post for a deep-dive series on container internals, starting with why chroot matters.
On this page
Container Internals Deep Dive — this post is part of a series
- Part 1: Container Internals Deep Dive 00
- Part 2: Container Internals Deep Dive 01: Cgroups
- Part 3: Container Internals Deep Dive 02: Namespaces
- Part 4: Container Internals Deep Dive 03: Network Namespaces and CNI
- Part 5: Container Internals Deep Dive 04: containerd Internals
- Part 6: Container Internals Deep Dive 05: OCI Standard
- Part 7: Container Internals Deep Dive 06: runc vs crun
- Part 8: Container Internals Deep Dive 07: Rootless Containers with Podman
- Part 9: Container Internals Deep Dive 08: Kata Containers
- Part 10: Container Internals Deep Dive 09: Firecracker microVM
Series: 1/10. This is the kickoff for the container internals deep-dive sequence.
Over the past year, I have been looking under the hood of Docker and related runtimes. This series is my attempt to share what I learned along the way. My goal is to start useful discussions, not just provide one-line answers.
Before we begin #
This series is for readers who want to understand how Docker works. For readers who just want to run Docker, getting started can be as easy as:
docker pull <image-name> # pull from Docker Hub
docker run <image-name> # run local image; pulls if missing
$ sudo docker run -it ubuntu bash
[sudo] password for dev:
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:latest
root@3fdeef5a4a1e:/#
That is enough to get moving. If you want to know what happens behind the scenes, welcome to this series.
History of Docker #
Why are we learning history of containers?
Because history is the best way to understand how we got here. It helps answer:
- Why do we do what we do?
- Why are containers designed this way?
What are containers? #
There are many definitions. Here is one from the Docker website:
A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings.
Important part: a container image is a lightweight, standalone, executable package of software.
A container is best thought of as a process (or process group) running with:
- Its own root filesystem view
- Isolation primitives such as namespaces and cgroups
- A constrained privilege model
Chroot #
One important early building block is the chroot system call.
chroot was added to Unix in 1979, originally to help with build and test workflows.
From the man page (man 2 chroot):
chroot - run command or interactive shell with special root directory
In short, chroot changes a process view of / (what it sees as the root filesystem).
By itself, chroot is not a complete security boundary, but it is a useful filesystem isolation primitive.
How chroot relates to containers #
Note: Links below point to source code you might enjoy exploring.
Moby Project is an open-source project created by Docker to enable and accelerate software containerization.
Docker Engine uses containerd for core lifecycle tasks through libcontainerd.
containerd invokes go-runc as part of runtime execution.
go-runc is a wrapper around runc.
runc init eventually reaches rootfs setup paths including chroot.
Summary:
- Docker CLI talks to
dockerdover Unix socket (/var/run/docker.sock). dockerdtalks tocontainerdover containerd’s gRPC API.containerdhandles tasks like image pull/storage and container lifecycle.- For OCI runtime execution,
containerdusesrunc(via go-runc wrapper). runcsets up root filesystem and isolation primitives, wherechrootcan be part of that setup.
So what? #
I could have stopped at “Docker uses chroot”, but the full spawn path is more interesting and more useful for debugging.
The key point: containers are not just chroot; they combine filesystem setup, namespaces, cgroups, and capability controls.
Why change root in first place? #
Changing the root view helps solve one of Docker’s core goals: avoid the classic “It works on my machine” problem.
To make workloads reproducible, we run them with an image-based root filesystem containing required dependencies.
With chroot and mount namespace setup, a process resolves paths inside container rootfs instead of host rootfs.
And much more #
This post is a kickoff for the series, with focus on why chroot still matters.
The original plan for next posts:
- Chroot (already discussed above)
- Cgroups (limit resource usage)
- Namespace (isolating containers running on same system)
- Network Namespaces and CNI (critical part of container ecosystem)
- containerd (details and internals)
- OCI standard (why and what of OCI)
- runc and crun (implementations of OCI and comparing them)
- Rootless containers (case study using podman)
- Kata containers
- Firecracker microVM
These are the first 10 planned topics. See you in the next post, where we start with cgroups.