DEV Community

Srinivasaraju Tangella
Srinivasaraju Tangella

Posted on

Inside Docker: The Complete Architecture Explained from CLI to Kernel

🚀 Introduction

Docker revolutionized the way applications are built, shipped, and run.
But beyond the buzzwords, few truly understand how Docker works under the hood — how a simple command like

docker run nginx

translates into a fully isolated process running inside a container.

This article takes you deep into Docker’s architecture, layer by layer — from the Docker CLI all the way down to Linux kernel namespaces and cgroups.


đź§© 1.What Is Docker?

Docker is an open-source containerization platform that packages an application and all its dependencies into a single unit called a container.
Containers ensure that applications run consistently across environments — whether it’s a developer’s laptop, an on-prem server, or a cloud VM.

At its core, Docker is a set of components working together:

Docker CLI → dockerd (Docker Daemon) → containerd → runc → Linux Kernel

Let’s decode each component in order.


🧰 2.Docker CLI – The User Interface

The Docker CLI (docker command) is the entry point for interacting with the Docker Engine.

Key Functions:

Accepts user commands (docker build, docker run, docker ps).

Sends them to the Docker Daemon via the Docker REST API over a Unix socket (/var/run/docker.sock).

When you run:

docker run nginx

You’re not directly running a container — you’re sending a JSON API request to the daemon.

CLI → Daemon communication:

/usr/bin/docker → /var/run/docker.sock → dockerd


⚙️ 3.Docker Daemon (dockerd) – The Brain

The Docker Daemon is the central control service of the Docker Engine.
It runs in the background, constantly listening for API requests from the CLI or other clients.

Key Responsibilities:

Build and manage Docker images.

Manage containers, networks, and volumes.

Handle authentication with registries (e.g., Docker Hub, JFrog, AWS ECR).

Communicate with containerd for container lifecycle management.

In essence, dockerd is the orchestrator that translates high-level user intent into low-level container actions.

Command to check if it’s running:

sudo systemctl status docker


⚙️ 4.containerd – The Runtime Manager

Originally a part of Docker, containerd is now a standalone CNCF project.
It’s the core container runtime interface used not only by Docker but also by Kubernetes.

Purpose:

containerd manages the entire lifecycle of a container:

Pulling/pushing images

Managing snapshots (layered filesystems)

Creating, starting, stopping, and deleting containers

Exposing an API for higher-level systems (like dockerd or Kubernetes)

When dockerd receives a command like “create a container,” it delegates that request to containerd.

containerd then invokes a low-level runtime (runc) to create the actual container process.

You can see it running:

ps aux | grep containerd


🧩 5.containerd-shim – The Silent Middle Layer

Each running container is managed by a small process called the containerd-shim.

Why It Exists:

Keeps containers running even if containerd or dockerd crashes.

Maintains the container’s STDIN/STDOUT streams.

Reaps zombie processes (acts as the parent process of the container).

So the runtime stack looks like this:

dockerd → containerd → containerd-shim → runc


⚙️ 6.runc – The Low-Level Runtime (The Actual Container Creator)

runc is the OCI (Open Container Initiative) compliant runtime responsible for actually creating containers.
It’s a lightweight binary that calls Linux kernel features to provide isolation.

What It Does:

Uses the clone() syscall to create a new process with isolated namespaces.

Applies cgroups for CPU, memory, and I/O limits.

Mounts the root filesystem using OverlayFS layers.

Executes the container’s default command (/bin/sh, nginx, etc.).

Then exits — leaving the container process running independently.

After container creation:

runc terminates.

The container continues running under containerd-shim.


🧠 7.Linux Kernel – The Real Workhorse

All the above components (dockerd, containerd, runc) are user-space tools.
The Linux kernel provides the fundamental mechanisms that make containers possible:

Kernel Feature Purpose

Namespaces Isolate processes, networks, filesystems, users, etc.
cgroups Control and limit CPU, memory, and I/O usage.
Capabilities Restrict root privileges.
OverlayFS / UnionFS Combine multiple image layers into a single root filesystem.


đź§± 8.Namespaces Explained (Isolation Layer)

Namespaces create isolated environments for each container.
Every container gets its own unique instance of several namespaces:

PID: Process IDs Each container has its own PID 1

NET: Network stack Own IP, interfaces, routing tables

MNT: Filesystem mounts Separate root filesystem

IPC: Inter-process communication Own shared memory

UTS: Hostname/domain Custom hostname

USER: User IDs Root inside container ≠ root on host

Check namespaces on a container:

ls /proc//ns


🔒 9.Control Groups (cgroups) – Resource Management Layer

While namespaces isolate, cgroups control.
They limit and monitor a container’s access to CPU, memory, block I/O, and network.

Example:

docker run --memory=512m --cpus=1 nginx

This enforces kernel-level resource limits using cgroups.


đź§© 10.The Full Execution Flow

Let’s connect everything together visually 👇

$ docker run nginx
│
├── Docker CLI
│ → Sends REST API to /var/run/docker.sock
│
├── Docker Daemon (dockerd)
│ → Parses config, checks image, calls containerd
│
├── containerd
│ → Manages lifecycle, sets up filesystem
│
├── containerd-shim
│ → Keeps container alive, manages IO
│
├── runc
│ → Creates container via kernel syscalls (clone, mount, setns)
│
├── Linux Kernel
│ → Enforces isolation (namespaces, cgroups)
│
└── Container
→ A running isolated process


🔍 11.Why Docker Split into These Components

Originally, Docker was monolithic — one big daemon handling everything.
To align with open standards and improve reliability, Docker adopted a modular runtime model based on OCI and CNCF specifications.

Layer Component Standard

High-level Docker Engine Docker API
Runtime manager containerd CNCF project
Low-level runtime runc OCI Runtime Spec

This modularity made it possible for Kubernetes to use containerd + runc directly, even without Docker.


⚡ 12.The Kernel-Primitives View

Ultimately, containers are just Linux processes with:

Isolated namespaces

Restricted resources via cgroups

Controlled capabilities

A layered filesystem

So docker run is essentially a user-friendly abstraction over a series of Linux system calls orchestrated by runc.


🧠 13.Recap – The Docker Architecture Hierarchy

Layer Component Role Runs In

1 Docker CLI Command interface User space
2 Docker Daemon (dockerd) API, orchestration User space
3 containerd Container lifecycle management User space
4 containerd-shim Process supervisor User space
5 runc Container creator User space → kernel
6 Linux Kernel Isolation (namespaces, cgroups) Kernel space


đź§© 14.Conclusion

Docker isn’t magic — it’s an elegant orchestration of Linux primitives wrapped in developer-friendly tooling.
Understanding its internal architecture gives you control over:

Performance tuning

Security hardening

Runtime debugging

Building your own container-based systems

When you think in terms of CLI → Daemon → containerd → runc → Kernel → Namespaces,
you’re no longer “using Docker” — you’re engineering with it.

Top comments (0)