Why Kubernetes Removed Docker

On December 2, 2020, the official Kubernetes blog published an article co-authored by ten community maintainers: Don’t Panic: Kubernetes and Docker.

Published on the same day, the Dockershim Deprecation FAQ made the formal announcement: dockershim, the Docker translation layer built into the kubelet, was slated for deprecation and eventual removal.

The announcement sparked widespread panic across the community, with many initially fearing that their existing Docker container images would no longer run on their clusters.

Yet artifacts built with Docker are fundamentally compliant with Open Container Initiative (OCI) specifications. Any CRI-compatible runtime can pull and run them directly; existing Dockerfiles and container images need no changes whatsoever.

On May 3, 2022, with the official release of Kubernetes 1.24, dockershim was permanently purged from the codebase, and the broader ecosystem completed a seamless transition.

Four years on, the transition has cemented containerd as the default runtime for most nodes and stands as a textbook case of Kubernetes stripping vendor-specific code out of its core to return to open, standardized interfaces.


Why CRI and Dockershim Existed

The Heavy Toll of Early Ad-Hoc Integrations

Kubernetes was not born in an era of mature container standards. The project’s core code was first pushed to GitHub on June 6, 2014 (as recounted in the official tenth-anniversary retrospective).

At the time, Docker held a near-monopoly on the container market, making direct, native support for Docker the most pragmatic shortcut for Kubernetes.

However, this hardcoded integration quickly spiraled into technical debt. Google engineer Yu-Ju Hong acknowledged the predicament in the official CRI announcement on December 19, 2016:

“However, both Docker and rkt were integrated directly and deeply into the kubelet source code through an internal and volatile interface. Such an integration process requires a deep understanding of Kubelet internals and incurs significant maintenance overhead to the Kubernetes community. These factors form high barriers to entry for nascent container runtimes.”

The CRI Spec, a Temporary Shim, and a Convoluted Call Chain

To decouple these layers, Kubernetes introduced the Container Runtime Interface (CRI) in version 1.5.

Defined using Protocol Buffers and gRPC, this open specification allowed the kubelet to drive any container runtime that implemented the CRI without requiring recompilation.

The problem was that Docker did not natively implement the CRI. As the official FAQ bluntly stated in 2020:

“Docker itself doesn’t currently implement CRI, thus the problem.”

To maintain backward compatibility for existing users, the community had to build a temporary, thin translation layer directly inside the kubelet: dockershim. It acted as a stand-in for Docker, intercepting and translating CRI calls.

 Legacy call chain (dockershim):
 kubelet --> dockershim (in-tree shim) --> Docker Engine
   --> containerd --> runc --> Container

 Modern call chain (native CRI):
 kubelet --(CRI gRPC)--> containerd / CRI-O
   --> runc --> Container

As the official “Don’t Panic” post explained, Docker’s appeal stemmed from its rich suite of user-friendly interfaces and developer conveniences. Kubernetes as an orchestration system, however, has no need for these human-centric features—a cluster only requires raw, low-level container lifecycle management.

Dockershim existed solely to tunnel through those layers of user-facing abstractions to reach the underlying containerd engine.

Every extra translation layer forced the kubelet to handle an additional set of API error codes and version mismatches. As long as this call chain remained, the kubelet core could never truly escape compatibility workarounds tailored to Docker’s quirks.


Two Parallel Threads of Standardization: The Demise of rkt and containerd’s Independence

rkt and the High Toll of the Standardization Race

Removing dockershim was the final milestone in Kubernetes’ long journey to eliminate external runtime special-casing.

Understanding this trajectory requires a look back at CoreOS rkt, once seen as Docker’s fiercest rival.

rkt championed a pod-native architecture and aggressively aligned with early OCI specifications, entering the CNCF incubator alongside containerd in early 2017 (as covered on the OCI official blog). The community even developed a dedicated CRI shim for it: rktlet.

Yet following Red Hat’s acquisition of CoreOS in 2018, engineering resources shifted en masse toward CRI-O and Podman. Community momentum around rkt evaporated, and its GitHub repository was officially archived on February 24, 2020.

The demise of rkt highlighted a sobering reality: even with an architecture closely aligned with open standards, a runtime project that loses commercial backing and community traction will inevitably be marginalized.

Donating containerd to the CNCF: The Prerequisite for Decoupling

Removing dockershim was only possible because the core container runtime was no longer the private property of Docker Inc.

In late 2016, Docker announced plans to spin off its core runtime into an open-source project named containerd (as reported by SD Times).

The project was formally donated to the CNCF on March 29, 2017, entering the incubation phase.

 Key containerd milestones:
 +-- Late 2016: Docker announces containerd spin-off
     and intent to donate
 +-- 2017-03-29: Formally accepted into CNCF (Incubating)
 +-- 2017-12-05: containerd 1.0 released
 +-- 2018-05-24: containerd 1.1 released with built-in
     CRI plugin (GA)
 +-- 2019-02-28: Passes all graduation criteria
     to officially graduate from the CNCF

Spinning off containerd and donating it to the CNCF decoupled higher-level developer tooling from the low-level execution engine.

When containerd 1.1 reached GA with a built-in CRI plugin on May 24, 2018, the kubelet gained a neutral, stable, and direct interface to the engine, without detouring through Docker.


Why Removal Was Inevitable: Technical Roadblocks and Organizational Incentives

Maintenance Overhead and Roadblocks to Modern Kernel Features

It is often assumed that raw performance was the driving factor behind removing dockershim.

While switching to a native CRI runtime is admittedly “technically faster and uses fewer resources” (to borrow the FAQ’s phrase), official announcements made it unmistakably clear: maintenance overhead was the real motivator.

The December 2020 FAQ stated plainly: “Maintaining dockershim has become a heavy burden on the Kubernetes maintainers.”

Even more critical were the architectural bottlenecks. As the updated FAQ in February 2022 pointed out, modern Linux kernel features like cgroups v2 and user namespaces were actively being implemented across newer CRI runtimes.

With cgroups v2, Docker had historically favored the cgroupfs driver and lagged behind in native cgroups v2 adoption. If the kubelet and Docker used mismatched drivers, nodes facing memory pressure suffered resource accounting conflicts—often leaving pods unable to restart.

With user namespaces, the CRI required fine-grained control to configure isolated UID/GID mappings on a per-pod or per-container basis to enable rootless workloads. The Docker daemon, by contrast, relied primarily on a host-wide --userns-remap setting, leaving it unable to meet the isolation requirements of multi-tenant pods.

Both capabilities required deep, direct manipulation of process boundaries. Wedged in the middle, dockershim created an insurmountable architectural roadblock; removing it was the only way node-level technology could continue to advance.

The Misalignment Between Product Scope and Maintenance Burdens

Technical debt often stems from deeper misalignments in organizational architecture and product goals. Ever since 2016, dockershim had been maintained entirely by Kubernetes community volunteers as in-tree code.

The primary focus of Docker Engine was developer experience: standalone CLIs, Docker Compose, and local workflow tooling. Implementing and maintaining a gRPC-based CRI interface added negligible value to Docker’s own commercial product.

Consequently, Docker Inc. enjoyed the market dominance that came with being the default Kubernetes integration, while the engineering cost of translation and compatibility fell squarely on the open-source community.

Once the community was no longer willing to shoulder that maintenance burden inside the kubelet core for free, the removal of dockershim became only a matter of time.

A Governance Grace Period Far Beyond Official Standards

Despite the decision to cut ties, the Kubernetes community provided an extraordinarily generous migration runway.

According to the Kubernetes API deprecation policy, a deprecated beta interface must be maintained for nine months or three releases.

Although dockershim was merely an internal component, the grace period it received far exceeded official standards:

 Deprecation and removal timeline:
 +-- 2020-12-02: "Don't Panic" blog post and FAQ published
 +-- 2020-12-08: Kubernetes 1.20 released;
     deprecation formally announced
 +-- 2020-12-29: KEP-2221 created to track
     removal milestones
 +-- 2022-01-07: Community confirms removal in 1.24;
     pledges one year of support for 1.23
 +-- 2022-05-03: Kubernetes 1.24 released;
     dockershim officially removed

Removal was initially targeted for v1.22 (with v1.23 as the earliest alternative), but the community proactively postponed it to v1.24 to give the ecosystem ample time to prepare.

From deprecation notice to final removal, the process spanned 17 months and four minor releases.

Tracked transparently through KEP-2221, this high-impact architectural shift followed a rigorous, public enhancement proposal process, showcasing open-source governance at its best.


The Removal Process: Communication, Handoff, and the 1.24 Release

A Nine-Post Relay to Quell Community Panic

To dispel fears that “Kubernetes was dropping support for Docker containers,” the community published seven dedicated blog posts and two release announcements over the course of 17 months.

The communication followed a carefully staged cadence: from the late-2020 wake-up call Don’t Panic, to mid-countdown readiness checks like Are you ready for dockershim removal, to the eve-of-release reminder Is Your Cluster Ready for v1.24?, culminating on release day with Kat Cosgrove’s historical retrospective.

These posts repeatedly clarified image compatibility, supplied command-line cheat sheets, and walked through migration paths. Giving users a year-and-a-half lead time backed by multi-wave communication became the gold-standard playbook for disruptive deprecations in Kubernetes.

Mirantis Steps In: An Out-of-Tree Off-Ramp with cri-dockerd

For environments with hard dependencies on Docker Engine features, the community’s answer was to move maintenance out-of-tree.

Just two days after the deprecation announcement, Mirantis—which had recently acquired Docker’s enterprise business amid the company’s restructuring—publicly announced on December 4, 2020 that it would partner with Docker to maintain an out-of-tree shim called cri-dockerd, preserving compatibility for existing customers.

This provided enterprise users reliant on Docker Engine with a compliant off-ramp, and the official Container Runtimes documentation continues to list it as a supported option.

The 1.24 Release and the Division of CNI Network Responsibilities

On May 3, 2022, Kubernetes 1.24 was officially released, and dockershim was deleted from the kubelet source code.

Removed alongside dockershim was the kubelet’s legacy network plugin pipeline—including flags such as --network-plugin=cni.

Under the legacy architecture, the kubelet invoked CNI plugins directly through dockershim. Under the native CRI architecture, CNI invocation is delegated entirely to the runtime itself (such as containerd’s CRI plugin or CRI-O parsing /etc/cni/net.d).

This clarified component boundaries: the kubelet could focus exclusively on pod lifecycle management and scheduling, no longer intervening in the direct execution of low-level networking binaries.


The Ripple Effects Across the Ecosystem

How the Big Three Managed Cloud Providers Converged

The migration timelines across the Big Three cloud providers clearly illustrated the ecosystem’s convergence on containerd:

Cloud ProviderDefault containerd VersionMigration Strategy and Timeline
Azure AKSKubernetes 1.19+Moved first, adopting containerd as the default runtime well before the official deprecation announcement
AWS EKSKubernetes 1.24+Retained Docker Engine through v1.23, then switched entirely to containerd by default starting in v1.24
Google Cloud GKEKubernetes 1.24+Deprecated Docker node images in v1.24 and automatically migrated remaining node pools on September 1, 2023

Smooth upgrades across the Big Three public clouds allowed the vast majority of managed cluster users to transition without disruption.

containerd continued to mature, launching its 2.0 LTS release in late 2024 with a release cadence closely aligned with Kubernetes, while CRI-O solidified its position as a robust, widely adopted alternative.

Cascading Impacts on Surrounding Node Tooling

Swapping out the container runtime was far more than replacing a binary; it triggered an overhaul across the entire operational tooling ecosystem surrounding the node.

First came the forced convergence on cgroup drivers. The official container runtimes documentation issued an explicit warning:

“It is critical that the kubelet and the container runtime use the same cgroup driver and have consistent configurations.”

When teams migrated to containerd, the industry-standard practice became setting SystemdCgroup = true in /etc/containerd/config.toml, putting an end to the operational chaos of running cgroupfs and systemd managers side by side.

Then came decoupling in-cluster CI/CD build patterns. The long-standing “Docker-outside-of-Docker” (DooD) pattern—mounting the host’s /var/run/docker.sock inside a pod—broke immediately once dockerd was gone, resulting in file-not-found errors.

WARNING

Audit your build pipelines before upgrading to 1.24: DooD-style CI jobs that mount /var/run/docker.sock break the moment dockerd is removed; switch to daemonless tools like Kaniko and Buildah so builds no longer depend on a host-level Docker daemon.

Finally came rearchitecting observability and logging pipelines. Log file paths shifted from the Docker-era /var/lib/docker/containers/<id>/<id>-json.log to the CRI-standardized /var/log/pods/. Monitoring DaemonSets that previously scraped the Docker daemon API directly had to be rewritten to interface with CRI-compatible endpoints.


Four Lessons in Platform Architecture Evolution

The retirement of dockershim leaves clear architectural principles for the evolution of cloud-native infrastructure:

  1. Fold in-tree special cases into standardized interfaces: The progression of “in-tree special case → standard interface → remove special case” has become an archetypal playbook in Kubernetes. Subsequent cloud provider integrations followed this exact trajectory under KEP-2395, which stripped provider-specific code out of the core tree into out-of-tree Cloud Controller Managers (CCMs).
  2. A generous runway is engineering governance’s first line of defense: Providing a 17-month transition window backed by nine official publications ensured the community wasn’t left behind. Fundamental architectural overhauls must grant the surrounding ecosystem ample breathing room to allow third-party distributions and tooling ecosystems to catch up.
  3. Let the market maintain the legacy off-ramps: Rather than banning a specific technology, the community leveraged standard interfaces to let commercial partners maintain cri-dockerd. This struck a balance between standardizing on the mainstream path and preserving long-tail enterprise compatibility.
  4. The decoupling dividends of standard interfaces: With Docker’s implementation specifics out of the picture, the division of labor between the kubelet and CRI runtimes became crisp and focused. This established a clean, extensible boundary for emerging runtimes on the node, including WebAssembly (via runwasi) and microVMs (via Kata Containers).