What Is containerd, in Depth?
A ground-up, zero-jargon tour of the quiet engine running underneath both Docker and Kubernetes — the industry-standard container runtime almost nobody talks about directly, yet almost everyone running containers today depends on completely.
Picture a busy, always-running international airport. Passengers see the airline check-in counter, the gate agent, the flight number on a departure board — the friendly, visible surface of “getting on a plane.” What they never see, and never need to think about, is the enormous machinery underneath that surface: ground crews physically moving aircraft, fuel trucks, baggage systems, air traffic control coordinating every single takeoff and landing across the entire airfield. The airline brand is what passengers experience. The airfield operations underneath are what actually make a plane leave the ground. In the world of containers, Docker is the airline brand most people recognize and interact with directly. containerd is the airfield operations underneath it — the actual machinery that pulls images, prepares filesystems, and gets a container process physically running on a real machine.
This is a complete, in-depth walkthrough of what containerd actually is, why it exists as its own separate piece of software rather than being permanently welded into Docker, how it is architected internally, what happens step by step when it starts a container, and how it quietly became the shared foundation underneath both Docker and Kubernetes at once. None of this requires prior experience with Docker, Kubernetes, operating systems internals, or the command line. Every technical term is explained the first time it appears, paired with a plain-language analogy and, wherever it helps, a real example drawn from how well-known technology companies actually run their infrastructure.
By the end, containerd should stop feeling like an obscure name occasionally glimpsed in a log file or a diagnostic command’s output, and start feeling like exactly what it is: the shared, dependable machinery an entire generation of container platforms quietly agreed to stop reinventing separately, choosing instead to build together, once, and trust collectively, layer by layer, from the gRPC API down to the single Linux process it eventually starts.
1What Is containerd, Really?
Before touring the internals, it helps to place containerd correctly in the stack of software that makes a container run at all — because it sits in a spot most beginners never realize exists.
When someone runs a container using Docker, they are typing a command into a tool called the Docker CLI, which talks to a background service called dockerd, the Docker Engine daemon. For a long time, Docker Engine handled everything itself, end to end — pulling images, managing storage, and actually starting processes. Over time, the Docker project deliberately pulled the lower, more fundamental parts of that job out into their own separate, independent piece of software, donated it to open, vendor-neutral governance, and gave it a name: containerd, pronounced “container-dee.” Docker Engine itself, today, is largely a friendly, feature-rich front end that talks to containerd underneath to actually get the real work done.
Think of a restaurant with a beautifully designed dining room, a menu, and friendly waitstaff taking orders — that is Docker’s user-facing experience. Behind a swinging door sits the actual kitchen, with its own trained staff, its own equipment, and its own well-defined procedures for turning a written order into a finished plate of food — that is containerd. Crucially, in a large restaurant group, several different dining rooms, under different brand names, might all share access to the exact same central kitchen, each sending it orders through the same standardized ticket system, without customers in any one dining room ever needing to know the kitchen is shared. This is almost exactly the relationship between containerd and the different “dining rooms” — Docker, Kubernetes, and others — that rely on it today. The kitchen itself does not care which dining room a ticket came from; it simply follows the same standardized recipe procedure every single time, which is exactly what lets a shared kitchen serve very different-looking dining rooms consistently and reliably.
containerd’s own official description of itself is refreshingly narrow and precise: it is “an industry-standard container runtime with an emphasis on simplicity, robustness, and portability.” It deliberately does not try to be a complete, end-user-friendly platform with a polished command-line experience, image-building tools, or a rich plugin marketplace the way Docker Engine does. Its job is narrower, deeper, and more foundational: managing the complete lifecycle of a container on a single machine — pulling and storing images, preparing a filesystem for a container to run in, and starting, stopping, and supervising the actual container process — and exposing all of that functionality through a clean, well-defined programming interface that other, higher-level tools can build on top of.
This deliberate narrowness of scope is itself a design philosophy worth naming directly, because it explains so much of what follows in later chapters. A tool that tries to do everything for everyone tends to accumulate assumptions specific to whichever use case its original authors had most in mind, which quietly makes it harder for a genuinely different use case to build on top of cleanly later. By staying narrowly focused on the fundamental lifecycle of pulling images and running containers, and leaving user experience, orchestration policy, and higher-level workflow entirely to whatever client sits above it, containerd avoids baking in assumptions that would favor one particular platform over another, which is exactly what makes it equally natural, rather than merely tolerable, as a foundation for both Docker’s own opinionated tooling and Kubernetes’s very different orchestration model.
To be clear from the very start, containerd is not a competitor to Docker; it is a piece Docker itself is built from. Understanding containerd is really understanding what “running a container” actually requires at a fundamental level, stripped of any single vendor’s particular user experience layered on top. This is exactly why the same containerd, running on the same machine, can quietly serve both Docker and Kubernetes at once, as later chapters explain in detail.
containerd is governed today by the Cloud Native Computing Foundation, the same vendor-neutral, non-profit organization that also oversees Kubernetes itself, and it has reached that foundation’s highest maturity level, called “graduated” status, reserved for projects considered stable, widely adopted, and production-proven at a very large scale. This governance detail matters practically, not just symbolically: it means containerd’s development and direction are not controlled by any single company, which is a large part of why competing platforms, including some that compete directly with Docker’s own tooling, are comfortable building on top of the exact same shared foundation.
It is worth being explicit about the historical motivation behind this extraction, because it explains a great deal about containerd’s present-day design priorities. Before containerd existed as a separate project, every tool that wanted to run a container on a machine effectively had to either depend on the full, monolithic Docker Engine, or reimplement a large amount of the same low-level plumbing itself, independently, with no guarantee that two separately reimplemented runtimes would behave identically in every edge case. This duplicated effort was genuinely wasteful across the industry, and it also meant subtle behavioral differences between runtimes could quietly cause a container that worked correctly under one platform to behave unexpectedly under another. Extracting the shared, foundational logic into containerd, and standardizing the interfaces around it, was a direct, deliberate response to exactly this problem.
2Architecture & Components: Client, API, Services, Shims, and Runtimes
containerd is built as a small core surrounded by a family of cooperating pieces, each with one clear, narrow job — understanding each piece is the fastest path to understanding the whole system.
containerd Daemon
The long-running background process itself, exposing a programming interface that any client, including Docker’s own engine, can talk to over a local socket.
gRPC API
A structured, efficient, well-documented interface that clients use to ask containerd to pull an image, create a container, start a process, and more.
Content Store & Snapshotters
Internal subsystems responsible for storing downloaded image data and turning stacked image layers into an actual, usable filesystem for a container.
Shim
A small, dedicated helper process, one per running container, that sits between containerd and the actual container process, keeping it alive independently.
OCI Runtime (runc)
The low-level program that does the final, literal work of actually creating an isolated process on the Linux kernel using namespaces and control groups.
CRI Plugin
A built-in plugin implementing the Container Runtime Interface, letting Kubernetes talk to containerd directly, in the vocabulary Kubernetes itself expects.
containerd is deliberately organized as a small, stable core surrounded by a family of internal services and plugins, each handling one distinct concern: an image service for pulling and tracking images, a content store for the raw, downloaded layer data, a snapshot service for turning those layers into a real, usable filesystem, a container service for tracking container configuration and state, and a task service for actually starting, stopping, and monitoring the running process itself. Every single one of these services is reachable through the same gRPC API, which is exactly what makes containerd genuinely usable as a foundation by different higher-level tools, rather than a system with Docker-specific assumptions baked invisibly into its core.
flowchart TB
A["Docker Engine (dockerd)"] -->|"gRPC"| C["containerd"]
B["Kubernetes Kubelet"] -->|"CRI, gRPC"| C
C --> D["Content Store"]
C --> E["Snapshotters"]
C --> F["containerd-shim-runc-v2"]
F --> G["runc"]
G --> H["Actual Container Process (Linux kernel)"]
Fig. 1 — Both Docker and Kubernetes talk to the same containerd daemon through well-defined APIs, which in turn delegates the actual process creation down to runc through a per-container shim.
This diagram is worth reading carefully, because the two arrows entering containerd from above are the whole story in miniature: two entirely different clients, built by different teams for different purposes, speaking to the exact same daemon through the exact same kind of structured API call. Neither Docker nor Kubernetes needs to know anything special about the other’s existence; each simply issues ordinary requests against containerd’s published interface, and containerd itself handles the rest identically regardless of which client happened to make the call.
The shim deserves special attention, because it explains one of containerd’s most practically valuable design decisions. Rather than having the containerd daemon itself directly parent every running container process, containerd spawns one small, lightweight shim process per running container, and that shim, not containerd itself, becomes the direct parent of the actual container process. The shim’s entire job is narrow: keep the container’s standard input and output streams connected, report the container’s exit status back to containerd, and keep the container running even if containerd itself is restarted or temporarily unavailable. This narrow, single-purpose design is deliberate, following the same minimal-surface philosophy that shapes containerd’s architecture at every layer.
Finally, runc is the piece that does the truly low-level work. It is a small, focused command-line tool implementing the Open Container Initiative runtime specification — a vendor-neutral, industry-agreed standard describing exactly how a container should be created on a Linux system. runc’s job, invoked by the shim, is to actually call into the Linux kernel and set up the isolation primitives — namespaces and control groups, covered in depth in Chapter 6 — and then start the container’s actual application process inside that isolated environment. Once that process is running, runc itself exits, leaving the shim as the process’s direct, lightweight supervisor.
It is worth noting explicitly that this entire architecture is built in deliberate, well-defined layers, each conforming to a published, vendor-neutral specification rather than a single company’s private implementation detail. The Open Container Initiative specification that runc implements exists specifically so that a container built and validated against one OCI-compliant runtime should behave identically under any other OCI-compliant runtime, and containerd’s own shim interface exists specifically so an entirely different runtime implementation can be swapped in without requiring any change at all to containerd itself, Docker, or Kubernetes above it. This layered, standards-based design is precisely what allows the pluggable, stronger-isolation runtimes discussed in Chapter 6 to exist as genuine drop-in alternatives rather than requiring an entirely separate, parallel ecosystem of tooling built around them.
3Internal Working: From Image Pull to a Running Process
Tracing exactly what containerd does, step by step, from receiving a request to a real process running on the machine, turns the architecture from Chapter 2 into a concrete, understandable sequence.
A client sends a request
Docker Engine, Kubernetes’s kubelet, or another client calls containerd’s gRPC API, asking it to pull a specific image.
The image service fetches layers into the content store
containerd downloads each of the image’s layers from a registry and stores their raw, compressed content in its own internal, content-addressed storage.
A snapshotter prepares a real filesystem
A snapshotter plugin, most commonly one built on the overlay filesystem covered in the article on Docker storage drivers, unpacks and stacks those layers into an actual, usable root filesystem.
A container object is created
containerd records the container’s configuration — which image, which snapshot, what command to run, what resource limits apply — as a tracked container object in its own metadata store.
A shim is spawned for the task
containerd starts a new, dedicated shim process specifically for this one container, handing it the container’s configuration and snapshot.
The shim invokes runc, then supervises directly
The shim calls runc, which sets up kernel-level isolation and starts the actual process; runc then exits, and the shim becomes that process’s direct, ongoing supervisor.
Because the shim, not containerd itself, is the actual parent process of a running container, containerd’s own daemon can be restarted, upgraded, or briefly crash, without killing any already-running container at all. Each shim keeps its own container alive independently and simply reconnects to containerd once it comes back, reporting the container’s current status. This single architectural decision is directly responsible for one of containerd’s most valued production properties: the ability to upgrade the container runtime itself without any workload downtime.
The task concept, introduced briefly above, is worth naming explicitly, because containerd deliberately separates it from the idea of a “container.” A container, in containerd’s own internal model, is really just a stored, persistent description — configuration, metadata, an associated snapshot — that can exist even while nothing is actually running. A task is the live, running execution of that container’s process at a given moment. This separation is what allows a container to be created, paused as a task, resumed, or even have its running task fail and be restarted, all while the container’s own underlying identity and configuration remain a single, stable, tracked object throughout.
This container-versus-task distinction is easy to underestimate on first read, but it has real, practical consequences worth spelling out. Because a container object can exist with no running task at all, containerd can represent a container that has been fully created and configured, with its filesystem snapshot already prepared, but deliberately not yet started — useful for platforms that want to prepare a container ahead of time and start it later with minimal delay. It also means that when a container’s process exits, whether cleanly or due to a crash, the task itself is what ends, while the container object, its configuration, and its filesystem snapshot can remain intact, ready to be inspected for diagnostic purposes or restarted as a fresh task without needing to rebuild the container’s configuration from scratch each time.
4Data Flow & Lifecycle: Image, Snapshot, Task, and Namespace
Zooming out from one single container to the full picture reveals how containerd organizes many containers, from many different clients, on one shared machine, without them stepping on each other.
containerd introduces its own concept of a namespace — not to be confused with the Linux kernel isolation namespaces covered in Chapter 6, though the two share a name and a similar spirit. A containerd namespace is a logical partition inside containerd’s own internal bookkeeping, letting completely separate clients — Docker Engine and Kubernetes’s kubelet, for instance — use the very same containerd daemon on the very same machine, storing and listing their own containers, images, and tasks, without ever seeing or interfering with each other’s. This is precisely what makes the shared-kitchen analogy from Chapter 1 literally true at the software level, not just a loose comparison.
sequenceDiagram
participant Client as Client (Docker or Kubelet)
participant CD as containerd
participant Snap as Snapshotter
participant Shim as Shim + runc
Client->>CD: Pull image (into client's own namespace)
CD->>CD: Store layers in content store
Client->>CD: Create container from image
CD->>Snap: Prepare root filesystem snapshot
Client->>CD: Start task
CD->>Shim: Spawn shim, invoke runc
Shim-->>CD: Report running status, exit code on completion
Fig. 2 — Every image, container, and task lives inside a specific containerd namespace, keeping different clients on the same machine fully isolated from each other’s bookkeeping.
Following one image’s full lifecycle end to end makes the whole picture concrete. An image begins as a reference to a specific, tagged artifact in a remote registry. Once pulled, its layers live permanently in containerd’s content store, addressed by their own content hash, exactly the same content-addressable principle covered in the discussion of Docker image layers. When a container is created from that image, a snapshotter produces a fresh, writable filesystem view built on top of those shared, read-only layers — the same layered, copy-on-write behavior described in the article on Docker storage drivers, just implemented here directly inside containerd’s own snapshot subsystem rather than through Docker Engine. When a task is started, that snapshot becomes the actual root filesystem the running process sees. When the container is eventually removed, its snapshot is released, though the underlying, shared image layers it was built from remain in the content store, ready to be reused instantly by the next container built from that same image.
Why This Lifecycle View Matters in Practice
Because Docker Engine and Kubernetes’s kubelet each operate in their own separate containerd namespace, a machine running both at once genuinely keeps their containers, images, and tasks bookkept separately, even though both are ultimately powered by the exact same underlying daemon, the exact same content store implementation, and the exact same snapshotting machinery. This is precisely how a single physical or virtual machine can, in principle, serve two entirely different orchestration systems through one shared, efficient runtime layer, without either system needing to know or care that the other is present.
The snapshotter layer deserves one further, closer look, because it is one of the more genuinely pluggable pieces of the entire architecture, in the same spirit as the runtime pluggability covered in Chapter 6. containerd does not hard-code one single way of turning stacked image layers into a usable filesystem; instead, it defines a snapshotter interface, and different snapshotter implementations can satisfy that interface using different underlying storage technologies — the overlay-filesystem approach covered in the article on Docker storage drivers is the most common default, but alternative snapshotters exist built on other filesystem technologies entirely, chosen for specific performance characteristics or specific storage hardware a given deployment happens to rely on. This mirrors, deliberately, the exact same design philosophy that produced the pluggable runtime layer: define a clear, stable interface, and let different implementations compete and specialize underneath it, rather than locking the entire system to one fixed choice made once and never revisited.
5Advantages, Disadvantages & Trade-offs
Pulling containerd out of Docker Engine and standardizing it as shared, vendor-neutral infrastructure was a deliberate trade, not an unambiguous, cost-free improvement.
Advantages
- One shared, well-tested runtime foundation instead of every platform reinventing container execution from scratch, reducing duplicated engineering effort industry-wide.
- Runtime upgrades and restarts do not kill already-running containers, thanks to the shim architecture described in Chapter 3.
- Native, first-class support for the Container Runtime Interface makes it a natural, efficient choice directly underneath Kubernetes, without an extra translation layer.
- Vendor-neutral governance under the Cloud Native Computing Foundation gives competing platforms genuine confidence in building on the same shared foundation.
Disadvantages & Trade-offs
- containerd alone provides no user-friendly command-line experience comparable to Docker’s own tooling, and is not really meant to be used directly by most end users.
- Its layered architecture, spread across several cooperating components, adds genuine conceptual complexity compared to a single, monolithic runtime that does everything itself.
- Debugging a problem can require understanding which of several layers — client, containerd itself, the shim, or runc — is actually responsible for a given symptom.
- Because it is a foundational, low-level layer, most application developers never need to interact with it directly, which means containerd knowledge is easy to overlook until something at this layer specifically goes wrong.
This industry-wide consolidation is worth appreciating on its own terms, separate from any single technical feature. Getting competing companies and open-source projects to agree on shared, foundational infrastructure, rather than each defending their own separately built version, is a genuinely difficult organizational achievement, not just a technical one, and containerd’s vendor-neutral governance was a deliberate, necessary condition for making that agreement possible at all.
The complexity trade-off deserves a direct, honest response, since it is the concern raised most often by newcomers. For an ordinary developer using Docker day to day, this layered architecture is almost entirely invisible — Docker Engine’s own tooling continues to present the same familiar experience it always has, with containerd doing its work silently underneath. The added complexity becomes practically relevant mainly for platform engineers and infrastructure teams operating container platforms at scale, for whom understanding this layered architecture directly is exactly the kind of depth that turns a mysterious, hard-to-diagnose production problem into a well-understood one, addressable at precisely the right layer instead of by guesswork.
There is a second, more subtle trade-off worth naming as well: standardizing on one shared, widely used runtime is a genuine strength in ordinary circumstances, but it also means a vulnerability or a serious bug discovered in containerd itself has an unusually wide blast radius, since it potentially affects every platform built on top of it at once, rather than being isolated to a single vendor’s own, separately maintained implementation. In practice, this concentrated exposure is offset by the intense, broad scrutiny a widely used, foundational open-source project like containerd naturally receives from a large, diverse community of contributors and security researchers, but it remains an honest, structural trade-off worth understanding rather than assuming away. Diversity in implementation offers some protection against a single point of failure; a shared, well-maintained standard offers protection through depth of scrutiny and consistency instead. Neither trade-off is free, and containerd’s design represents a deliberate bet on the latter.
6Security: Namespaces, cgroups, and Isolation Boundaries
containerd itself does not invent container isolation; it orchestrates and configures kernel features that have existed in Linux for years, through runc, and its own architecture adds a distinct security boundary of its own.
The actual isolation a container experiences comes from two Linux kernel features that runc configures on containerd’s behalf: namespaces, which give a process its own private, restricted view of specific system resources — its own process list, its own network interfaces, its own filesystem mount points — so it cannot see or affect what is happening in other containers or on the host outside its own view, and control groups, commonly called cgroups, which limit and account for how much of a shared physical resource — CPU time, memory, disk input and output — any given process is allowed to consume, preventing one container from starving every other container sharing the same machine.
Namespaces are like giving each tenant in an apartment building their own private door, their own private mailbox, and their own private address, even though the building itself is one shared physical structure — each tenant genuinely cannot see into another tenant’s apartment just by living in the same building. Control groups are like the building’s shared utility meters and circuit breakers, which cap how much electricity or water any single unit can draw at once, so one apartment running every appliance at full blast cannot cause the whole building’s power to fail for everyone else.
A container’s isolation is only as strong as the shared Linux kernel underneath every container on that machine. Every container on a given host, regardless of which orchestrator or runtime configured it, shares that one kernel, which means a genuine kernel-level vulnerability can, in principle, be exploited from inside a container to affect the host or other containers. Namespaces and control groups are strong, well-tested isolation mechanisms, but they are not an absolute, mathematically airtight security boundary the way running a fully separate virtual machine would be, which is precisely why additional runtime security layers exist for especially sensitive workloads.
This last point is exactly why containerd’s own architecture deliberately supports pluggable runtimes rather than hard-coding runc as the only possible option. Because the shim layer described in Chapter 2 defines a well-specified interface rather than assuming runc specifically, containerd can just as easily hand a container off to an alternative runtime implementation — one that runs each container inside its own lightweight, hardware-isolated micro virtual machine instead of relying purely on shared-kernel namespaces, for workloads where that stronger isolation boundary is worth the additional resource cost. This pluggability is a direct, practical consequence of the same shim architecture originally motivated by upgrade safety in Chapter 3, now also serving a distinct, valuable security purpose.
Namespaces
Give each container its own private, restricted view of processes, network, and filesystem mounts, hiding it from other containers.
Control Groups
Cap and account for how much CPU, memory, and disk I/O any single container can consume from the shared host.
containerd Namespaces
Keep different clients’ containers and images bookkept separately within containerd’s own internal metadata, though this is an organizational, not a security, boundary.
Pluggable Runtimes
Allow a stronger, hardware-backed isolation runtime to be used in place of standard shared-kernel namespaces for especially sensitive workloads.
It is worth being precise about one common point of confusion here: containerd’s own internal namespace concept from Chapter 4, used to separate Docker’s bookkeeping from Kubernetes’s bookkeeping on a shared machine, is an organizational convenience, not itself a security isolation boundary. It does not, on its own, prevent a process running inside one container from reaching another container through kernel-level namespaces and control groups, which remain the actual enforcement mechanism regardless of which containerd namespace either container’s metadata happens to be recorded under.
It is also worth being clear about where containerd’s own security responsibility genuinely does begin and end. containerd itself does not decide, on its own initiative, which namespaces or control-group limits a given container should receive; that configuration is supplied to it by the client making the request — Docker Engine, Kubernetes’s kubelet, or another caller — based on that client’s own configuration and policy. This means a meaningful share of a container’s actual real-world security posture is determined above containerd, by whichever client is requesting the container in the first place, and containerd’s own responsibility is faithfully and correctly carrying out whatever isolation configuration it is asked to apply, through runc, exactly as specified, every single time, with no silent shortcuts or inconsistencies.
7Monitoring, Logging & Metrics for containerd
Because containerd sits underneath both Docker and Kubernetes, its own health directly affects everything running above it — making it a layer worth monitoring in its own right, not just observing indirectly through the platforms built on it.
containerd exposes a structured, built-in event stream, reporting every meaningful state change — an image pull starting and completing, a container being created, a task starting, exiting, or being deleted — as a discrete, timestamped event. This event stream is precisely how higher-level platforms like Docker Engine and Kubernetes’s kubelet stay informed about what is actually happening at the runtime layer in real time, rather than having to repeatedly poll and ask. A team operating containerd directly can consume this same event stream to build monitoring and alerting specifically at the runtime layer, catching a runtime-level problem before it surfaces confusingly as an application-level symptom several layers higher up.
A second, practically important signal is shim process count, compared against how many containers should actually be running according to containerd’s own tracked state. Because each running container has its own dedicated shim, a mismatch between expected and actual shim counts is a strong, direct signal of a runtime-layer problem — an orphaned shim left behind after its container was supposed to be cleaned up, or a shim that failed to start correctly, leaving a container stuck in a state that neither Docker nor Kubernetes above it can fully explain from their own vantage point alone. This kind of mismatch is exactly the sort of problem that looks, from several layers above, like a vague, unexplained “container won’t start” or “container won’t stop” report, when the actual root cause is fully visible and easily diagnosed the moment someone checks shim state directly at the runtime layer instead.
A third is containerd’s own API responsiveness, since every operation any client performs — Docker starting a container, Kubernetes’s kubelet checking on a pod — ultimately depends on containerd’s gRPC API responding promptly. A machine under heavy load, with containerd itself struggling to keep up, can manifest as slow, seemingly unrelated symptoms several layers above — slow container starts, delayed status updates — that are much faster to diagnose correctly once containerd’s own API latency is tracked directly rather than inferred indirectly from higher-level platform behavior.
Platform engineering teams operating container infrastructure at scale increasingly treat containerd’s own event stream and API health as first-class, directly monitored signals, precisely because a problem that begins at this shared, foundational layer can otherwise masquerade as a confusing, hard-to-pin-down issue in whatever orchestration platform happens to sit above it.
A fourth signal worth tracking, closely related to shim count, is snapshot storage growth over time. Because every container created draws on the snapshotter subsystem described in Chapter 4 to prepare its writable filesystem view, a machine that creates and removes many containers over its lifetime, without those snapshots being reliably cleaned up when their containers are removed, can quietly accumulate unused, orphaned snapshot data in exactly the same way stale, dangling image layers can accumulate at the Docker level, as covered in the article on image layer optimization. Tracking snapshot storage directly, separately from general host disk usage, surfaces this specific failure mode early, before it manifests only as a confusing, unexplained disk-space crisis with no obvious single cause.
8Design Patterns Around containerd
These are the proven, repeatable patterns experienced platform teams reach for when building on or operating containerd directly, roughly in order of impact.
What It Is
Deliberately choosing containerd as the single, shared container runtime across an organization’s entire container infrastructure, whether workloads are ultimately managed through Docker, Kubernetes, or another platform, rather than running several different, incompatible runtimes side by side without a clear reason.
Why It Works
One shared runtime means one consistent set of operational knowledge, one consistent set of monitoring and troubleshooting practices, and one place where a security or performance improvement benefits every workload on that machine at once, regardless of which higher-level platform happens to be managing any specific container.
What It Is
Using containerd’s pluggable runtime support to explicitly select a stronger, hardware-backed isolation runtime specifically for workloads that process untrusted input or run multi-tenant code, while leaving lower-risk, trusted workloads on the standard, lighter-weight runc-based path.
Why It Works
Stronger isolation carries a real resource and performance cost, so applying it universally is often wasteful, while applying it nowhere leaves genuinely higher-risk workloads under-protected. Choosing deliberately, per workload, based on an honest assessment of trust and risk, gets the actual benefit of stronger isolation exactly where it is worth paying for.
What It Is
Building dedicated observability specifically around containerd’s own event stream, shim health, and API latency, as described in Chapter 7, rather than relying solely on whatever monitoring Docker or Kubernetes happens to surface from several layers above.
Why It Works
A runtime-layer problem is diagnosed far faster when it is visible directly at the layer where it originates, rather than reconstructed indirectly from confusing, several-steps-removed symptoms observed only at the application or orchestration layer above it.
What It Is
Deliberately relying on and testing the shim-based upgrade safety described in Chapter 3, confirming that already-running containers genuinely survive a containerd restart or upgrade, and building routine, low-drama runtime upgrades into normal maintenance practice rather than treating them as high-risk events requiring extensive workload downtime.
Why It Works
containerd was specifically architected to make this safe. A team that still schedules disruptive downtime windows purely out of caution for routine runtime upgrades is paying an operational cost the architecture itself was designed to eliminate.
Together, these four patterns describe a coherent operational posture: consolidate on one well-understood shared foundation, apply stronger isolation deliberately rather than universally or never, observe the runtime layer directly rather than only through higher-level proxies, and trust — while still verifying — the specific architectural guarantees containerd was built to provide.
A fifth pattern worth naming explicitly is routine snapshot and content garbage collection, building directly on the monitoring signal introduced in Chapter 7. Rather than waiting for accumulated, orphaned snapshot data to become a visible disk-space emergency, mature teams schedule regular, automated cleanup of unused snapshots and unreferenced content-store entries, mirroring the same proactive cleanup discipline recommended at the Docker image layer in earlier articles, applied here directly at containerd’s own storage layer instead. This pattern reinforces the third pattern above particularly well, since the same monitoring that surfaces snapshot growth as a signal is exactly what confirms scheduled cleanup is actually working as intended, rather than silently falling behind unnoticed.
9Anti-Patterns: What Quietly Undermines a containerd Deployment
Every one of these anti-patterns is the exact mirror image of a pattern from Chapter 8, which is why recognizing the pattern makes the anti-pattern obvious.
Running Multiple, Uncoordinated Runtimes
Letting different teams or platforms independently install and manage separate, inconsistent container runtimes on the same infrastructure, with no shared operational standard between them.
Universal or Nonexistent Strong Isolation
Either applying a heavier, hardware-backed isolation runtime to every single workload regardless of actual risk, wasting resources broadly, or never using it anywhere at all, leaving genuinely high-risk workloads under-protected by default.
Treating the Runtime Layer as a Black Box
Only ever monitoring Docker or Kubernetes-level metrics, with no direct visibility into containerd’s own event stream, shim health, or API latency, leaving runtime-layer problems to be diagnosed the slow, indirect way every time.
Fearing Routine Runtime Upgrades
Scheduling unnecessary, disruptive downtime windows for ordinary containerd upgrades out of unverified caution, missing out on the specific upgrade-safety guarantee the shim architecture was built to provide.
Assuming containerd Namespaces Provide Security Isolation
Mistakenly treating containerd’s own internal namespace bookkeeping, covered in Chapter 4, as a genuine security boundary between workloads, when the actual enforcement always comes from kernel-level namespaces and control groups instead.
Ignoring Orphaned Shims
Never checking for shim processes left running with no corresponding, actively tracked container, letting stale, orphaned processes quietly accumulate and consume resources over time.
No Snapshot or Content Garbage Collection
Never scheduling routine cleanup of unused snapshots and unreferenced content-store entries, letting orphaned storage accumulate silently until it surfaces only as an unexplained disk-space emergency.
Every anti-pattern above traces back to treating containerd as an invisible implementation detail rather than as production infrastructure in its own right, deserving the same deliberate operational attention — standardization, monitoring, and an honest, workload-by-workload risk assessment — as any other foundational system a team’s entire container platform actually depends on.
10Best Practices & Common Mistakes
A condensed, practical checklist — what to consistently do, and where teams most often slip.
| Practice | Why It Helps | Common Mistake to Avoid |
|---|---|---|
| Standardize on containerd as the shared runtime | Consolidates operational knowledge and consistent behavior across every platform running on top of it | Running several different, uncoordinated container runtimes side by side with no shared standard |
| Select a stronger isolation runtime deliberately, per workload | Applies stronger isolation exactly where actual risk justifies its resource cost | Applying it universally, wastefully, or not at all, leaving high-risk workloads exposed |
| Monitor containerd’s own event stream and API health | Diagnoses runtime-layer problems directly, at their true source | Relying solely on Docker or Kubernetes-level metrics several layers removed |
| Trust and routinely exercise shim-based upgrade safety | Turns runtime upgrades into low-risk, ordinary maintenance rather than dreaded events | Scheduling unnecessary downtime windows for routine containerd upgrades |
| Understand the true security boundary is the kernel, not containerd namespaces | Prevents a false sense of security from an organizational, not enforcement, mechanism | Treating containerd’s own internal namespaces as a genuine isolation boundary |
| Periodically check for orphaned shim processes | Prevents stale, untracked processes from quietly accumulating and consuming resources | Never auditing shim count against actively tracked, expected container count |
| Keep containerd itself current with upstream security patches | Ensures the shared foundation underneath every container platform on the machine stays secure | Patching Docker or Kubernetes regularly while neglecting the runtime layer underneath both |
| Schedule routine snapshot and content garbage collection | Prevents orphaned storage from silently accumulating into an unexplained disk-space crisis | Only addressing storage cleanup reactively, once disk space is already critically low |
Operating containerd well resembles how a large hospital treats its central power and water utility infrastructure, not how it treats any one specific department’s equipment. No single department “owns” the building’s electrical system, yet every department depends on it completely, and the hospital’s facilities team monitors it directly, maintains it on a predictable schedule, and treats any anomaly there as urgent precisely because a problem at that shared, foundational layer can silently affect every department at once. Treating containerd with that same foundational-infrastructure mindset, rather than as an invisible detail nobody needs to think about, is exactly the discipline this chapter describes.
11Real-World & Industry Examples
containerd’s journey from an internal Docker component to a shared, industry-standard foundation is one of the most well-documented examples of successful, vendor-neutral infrastructure collaboration in the entire container ecosystem.
Docker Inc. — Extracting and Donating containerd
Docker’s own engineering team made the deliberate decision to extract containerd out of the monolithic Docker Engine and donate it to open, vendor-neutral governance, explicitly recognizing that a shared, well-tested runtime foundation would benefit the entire container ecosystem more than keeping that same functionality permanently locked inside one company’s own product.
Kubernetes — Adopting containerd as a First-Class Runtime
The Kubernetes project’s own kubelet component talks directly to containerd through the Container Runtime Interface described in Chapter 2, and containerd has become one of the most widely deployed runtimes underneath production Kubernetes clusters industry-wide, precisely because it implements that interface natively and efficiently rather than requiring an extra translation layer.
Amazon Web Services — containerd Underneath Managed Container Services
Major cloud providers running large-scale, managed container platforms have adopted containerd as core infrastructure underneath their own services, benefiting directly from its shim-based upgrade safety described in Chapter 3 to patch and upgrade the runtime layer across enormous fleets of machines without disrupting customer workloads running on top.
The Cloud Native Computing Foundation — Graduation as a Trust Signal
containerd’s graduation to the Cloud Native Computing Foundation’s highest maturity tier, alongside Kubernetes itself, served as a concrete, public trust signal that helped convince other competing platforms and cloud providers to build on the same shared foundation rather than each maintaining their own separate, proprietary runtime implementation.
Independent Container Platforms — A Shared Foundation Across Competing Products
Several container platforms that directly compete with Docker’s own commercial tooling have, at the same time, adopted containerd as their own underlying runtime, a pattern only made possible by containerd’s vendor-neutral governance described in Chapter 1 — competitors are willing to depend on shared, foundational infrastructure specifically because no single competitor controls its direction, letting genuine competition happen at the higher, user-facing layers instead of being duplicated wastefully at the low-level runtime layer that all of them ultimately need.
12Frequently Asked Questions
For most ordinary development and application work, no. Docker Engine’s own tooling continues to provide the familiar, complete experience most users interact with, and it talks to containerd underneath entirely on its own. Direct, hands-on knowledge of containerd becomes genuinely valuable specifically for platform engineers and infrastructure teams operating container platforms at scale, or anyone diagnosing a problem that turns out to live at the runtime layer itself.
No — this is one of the most common misconceptions. containerd is deliberately built as vendor-neutral, shared infrastructure, and Kubernetes talks to it directly and natively through the Container Runtime Interface, without needing Docker Engine in the middle at all. Several other platforms and cloud services also build directly on containerd as their own underlying runtime.
containerd is the higher-level daemon managing the complete lifecycle of images, containers, and tasks, exposing a rich API that clients like Docker and Kubernetes talk to. runc is a much smaller, lower-level tool that containerd, through its shim, actually invokes to perform the final, literal work of creating an isolated Linux process using namespaces and control groups. containerd manages the overall lifecycle; runc executes one specific, narrow step within it.
Primarily for upgrade and restart safety, as covered in Chapter 3. Because each running container’s true, direct parent process is its own dedicated shim rather than containerd itself, the containerd daemon can be restarted or upgraded without killing any already-running container, since each shim keeps its own container alive independently and simply reconnects once containerd comes back.
Yes, through its pluggable runtime support described in Chapters 6 and 8. Because the shim layer defines a well-specified interface rather than assuming a single, fixed runtime implementation, containerd can hand a container off to an alternative runtime that provides stronger, hardware-backed isolation, such as running each container inside its own lightweight virtual machine, for workloads where that additional isolation is worth its resource cost.
Docker Engine’s own image storage is, today, actually built directly on top of containerd’s own content store and snapshot subsystems described in Chapter 2, rather than being a separate, parallel implementation. This is exactly why an image pulled through Docker and an image pulled through Kubernetes on the same machine, if built from the same underlying layers, can share the very same stored content in containerd’s content store rather than being duplicated.
Architecturally, yes, since containerd’s own namespace mechanism from Chapter 4 is specifically designed to let multiple independent clients share one daemon while keeping their bookkeeping separate. In practice, most production Kubernetes deployments today run containerd directly, without Docker Engine in the middle at all, since Kubernetes talks to containerd natively through the Container Runtime Interface and does not require Docker’s own additional layer to function.
Because each running container’s direct parent process is its own dedicated shim rather than containerd itself, as explained in Chapter 3, the container’s actual process generally keeps running completely undisturbed even if containerd crashes. Once containerd restarts, it reconnects to the still-running shims, recovering its own view of exactly which containers and tasks are currently active without needing to restart any of them. This exact behavior is what makes containerd upgrades and even unexpected crashes far less disruptive than they would be under a simpler, monolithic architecture where the runtime daemon itself directly parented every container process.
Not necessarily, though the everyday experience does change. containerd ships with its own, much lower-level command-line client, intended primarily for debugging and direct interaction by platform engineers rather than as a full replacement for Docker’s own polished, feature-rich tooling. Most teams that adopt containerd directly do so specifically in a Kubernetes context, where Kubernetes’s own tooling becomes the primary day-to-day interface, with containerd itself operating silently underneath, much the same way it already does underneath Docker.
13Summary and Key Takeaways
The chapters above moved from what containerd is, through its architecture, its internal mechanics, its trade-offs, its security model, its operational signals, its design and anti-patterns, and finally its real-world adoption across the industry — the same order a platform engineer would naturally want to learn it in, building each layer of understanding on the one directly before it.
Key Takeaways
- containerd is the shared, industry-standard container runtime underneath both Docker and Kubernetes, extracted deliberately from Docker Engine and donated to vendor-neutral, Cloud Native Computing Foundation governance.
- Its architecture is a small core surrounded by cooperating services and plugins — a content store, snapshotters, a task service, and native Container Runtime Interface support — all reachable through one consistent gRPC API.
- A dedicated shim process per running container is the key to safe, zero-downtime runtime upgrades, since each shim, not containerd itself, is the actual, direct parent keeping a container alive.
- containerd’s own internal namespaces let multiple clients, such as Docker and Kubernetes, share one daemon on one machine while keeping their images, containers, and tasks bookkept completely separately.
- Real isolation comes from the Linux kernel’s namespaces and control groups, configured by runc — containerd’s own internal namespace concept is an organizational convenience, not itself a security boundary.
- Pluggable runtime support lets stronger, hardware-backed isolation be applied deliberately, per workload, based on actual risk, rather than universally or never at all.
- Real organizations — Docker Inc., the Kubernetes project, major cloud providers, competing container platforms, and the Cloud Native Computing Foundation itself — all directly shaped and continue to rely on containerd, making it one of the most consequential, widely trusted pieces of shared infrastructure in the entire modern container ecosystem.
- Routine snapshot and content garbage collection deserves the same operational discipline as image layer hygiene at the Docker level, since unused storage at containerd’s own content store and snapshot layers can accumulate silently until it becomes a genuine, unexplained disk-space emergency.
- containerd’s deliberately narrow scope, standards-based interfaces, and vendor-neutral governance together explain why very different platforms, and even direct commercial competitors, are comfortable building on the exact same shared foundation without hesitation, trusting a piece of infrastructure none of them individually controls.