Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Docker Architecture Components

Recommended resources

Welcome to the next step in our journey to master Docker. In the previous lessons, you've learned what containerization is by comparing it to virtual machines, and you've explored why it's so transformative for software development and DevOps workflows. You know that containers solve the "it works on my machine" problem by creating consistent, portable environments.

Today, we're going to peel back the cover and investigate how Docker actually accomplishes this. We'll move from the high-level benefits to the concrete technical architecture. Your goal for this lesson is to identify the core components of the Docker platform: the client, the daemon (dockerd), containerd, and runc. Understanding this chain of command is crucial for diagnosing issues and appreciating the elegant, modular design of modern container systems.

The Client-Server Model

At a high level, Docker operates on a client-server architecture, a model you're likely familiar with from your experience with web applications. When you type commands in your terminal, you're not interacting with the container engine directly. Instead, you're using a client to send instructions to a long-running background service.

  • The Docker Client (docker): This is the command-line tool you interact with. When you type a command like docker run or docker build, the client's primary job is to take your instructions, package them into a standardized format, and send them to the server component.
  • The Docker Daemon (dockerd): This is the server, the "brain" of the operation. It's a process that runs continuously on your host machine, listening for API requests from the Docker client. It's responsible for managing all the Docker objects: images, containers, networks, and volumes.

The client communicates with the daemon via a REST API. On a typical Linux installation, this communication happens over a Unix socket located at /var/run/docker.sock, not over a network, which is fast and secure.

To get a clear picture of this initial interaction, let's consult a resource that breaks down the components.

How Docker Actually Works: A Deep Dive into the Internals

This article by Piyush Jajoo provides an excellent, illustrated breakdown of the Docker stack. We'll start by focusing on the two highest-level components.

First, read the section on the client. This reinforces its role as an API client. Next, read the section on the daemon. Pay close attention to the crucial point it makes: the daemon manages the lifecycle but now delegates the actual running of containers to other components.

This separation between the client and the daemon is powerful. It means you can have the Docker client on your local machine control a Docker daemon running on a remote server, which is a common pattern in many development and operational workflows.


Under the Hood: The Runtime Layers

As the reading mentioned, a significant architectural change happened in Docker a few years ago. The Docker daemon was split into smaller, more focused components, following the Unix philosophy of "do one thing and do it well." The task of actually running containers was handed off to a standardized, modular runtime stack.

This stack is composed of several layers, each with a specific responsibility. Let's look at the overall flow.

This diagram illustrates the flow of control in the Docker architecture. A user command travels from the client, through the Docker Engine, down to the container runtime layers (`containerd` and `runc`), and finally results in a running container process.

As you can see, when the dockerd daemon gets a request to run a container, it doesn't execute it directly. Instead, it makes a call to a component called containerd.

containerd: The High-Level Runtime

containerd is what's known as a high-level container runtime. Its purpose is to manage the complete container lifecycle. Think of it as a supervisor for containers. Its responsibilities include:

  • Pulling container images from registries.
  • Managing image storage.
  • Setting up container networking.
  • Supervising the execution of containers.
  • Exposing its own API for tools like dockerd to use.

Crucially, containerd is a separate, open-source project hosted by the Cloud Native Computing Foundation (CNCF). This means it's an industry standard, used not only by Docker but also by Kubernetes and other container orchestrators.

runc: The Low-Level Runtime

Even containerd doesn't get its hands dirty with the lowest-level details. For the actual creation of a container, it delegates to runc.

runc is a low-level container runtime. It is a small, lightweight command-line tool with a single, focused purpose: to create a container. It takes a container configuration bundle (a JSON file and a root filesystem) and uses the specific features of the Linux kernel—namely namespaces and cgroups—to spawn an isolated process. After creating the container and starting the process, runc exits.

containerd-shim: The Missing Link

There's one more crucial piece that connects these components: the containerd-shim. When containerd wants to run a container, it doesn't call runc directly. Instead, it launches a containerd-shim process for each container. This shim then executes runc to create the container.

Why this extra layer? The shim allows for daemonless containers. Once runc exits, the shim process remains as the parent of the container's main process. This de-couples the running container from the containerd and dockerd daemons. If you need to restart or upgrade the Docker daemon, your running containers are unaffected because the shim process keeps them alive. This is a critical feature for long-running production workloads.

The following video provides an excellent walkthrough of this runtime stack, explaining the roles and relationships between containerd, the shim, and runc.

What ARE Containers? (and Docker ...)

John Savill's Technical Training channel offers a clear, diagram-based explanation of how these runtime components work together.

Watch the segment starting from "What is the container runtime?". Pay close attention to the distinction between the high-level and low-level runtimes, and the explanation for why the shim process is so important for decoupling the container from the daemon.


The Full Picture: Tracing docker run

Now, let's put it all together and trace the journey of a single docker run command through the entire stack.

  1. You: You type docker run -it ubuntu bash into your terminal.
  2. Docker Client: The docker CLI parses your command, converts it into a REST API call, and sends it to the dockerd process via the /var/run/docker.sock socket.
  3. Docker Daemon (dockerd): The daemon receives the API call. It handles the high-level logic. It checks if the ubuntu image exists locally. If not, it pulls it from Docker Hub. It then prepares the container's configuration.
  4. The Hand-off: dockerd communicates with containerd (via an API called gRPC) and says, "Please start a container using this image and this configuration."
  5. containerd: The high-level runtime takes over. It creates the necessary OCI-compliant bundle for the container and tells a containerd-shim process to start.
  6. containerd-shim: This new shim process calls runc.
  7. runc: The low-level runtime receives the configuration. It interacts with the Linux kernel to create all the necessary namespaces (for process, network, and file system isolation) and apply cgroup limits (for resource control). It then executes the bash command inside this isolated environment. Once the process is running, runc exits.
  8. The Running Container: The bash process is now running inside the container. Its parent process is not runc or containerd, but the dedicated containerd-shim process, which continues to monitor the container and report its status back up the chain.

This modular, layered approach is a masterpiece of software engineering. It allows each component to be developed, updated, and even replaced independently, fostering a flexible and robust ecosystem.


Conclusion

In this lesson, we've unpacked the "black box" of Docker and identified the distinct components that work together to run containers.

Here are the key takeaways:

  • Client-Server Model: Docker uses a client (docker CLI) that sends commands to a server (dockerd daemon) via a REST API.
  • Layered Architecture: The Docker daemon delegates the work of running containers to specialized, standardized components.
  • containerd: A high-level runtime that manages the complete container lifecycle, from image pulling to execution.
  • runc: A low-level runtime that is responsible for the single task of creating a container by using Linux kernel features like namespaces and cgroups.
  • containerd-shim: A crucial process that sits between containerd and the container, enabling daemonless operation and making the system more resilient.

You now have a mental model of the entire chain of command, from your terminal to a running process isolated inside a container.

In our next lesson, you will install Docker Engine on your system. With this architectural knowledge, you'll be well-prepared to run your first container and trace the steps as they happen, connecting the theory to practice.