Sometimes you genuinely want Docker Engine as the Kubernetes node runtime — a team standardized on the Docker CLI/API for tooling, an image-build box that doubles as a node, or a legacy playbook you can't rewrite yet. Since dockershim was removed in Kubernetes 1.24, that path now runs through cri-dockerd, an open-source CRI adapter maintained by Mirantis. Here's how to set it up on Ubuntu, and an honest note on whether you should.
Should you? A quick reality check
For most people the answer is no — use containerd (see the previous post). Reach for cri-dockerd only when you have a concrete reason:
- Existing automation that talks to the Docker socket on the node.
- You want
docker buildand the kubelet on the same host sharing one image store. - A vendor or product that still assumes Docker Engine as the runtime.
If none of those apply, containerd is less to install and less to break.
1. Install Docker Engine on Ubuntu
Use Docker's official repo, not the docker.io package, so you get current Engine:
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
Set Docker's cgroup driver to systemd — on Ubuntu's cgroup v2 this must match the kubelet or the node will not stay Ready:
sudo mkdir -p /etc/docker
cat <<'EOF' | sudo tee /etc/docker/daemon.json
{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": {"max-size": "100m"} }
EOF
sudo systemctl restart docker
sudo systemctl enable docker
2. Install cri-dockerd
Grab the latest release for your architecture:
VER=0.3.15 # check github.com/Mirantis/cri-dockerd/releases for current
ARCH=$(dpkg --print-architecture)
curl -fsSLo cri-dockerd.deb \
"https://github.com/Mirantis/cri-dockerd/releases/download/v${VER}/cri-dockerd_${VER}.3-0.ubuntu-noble_${ARCH}.deb"
sudo dpkg -i cri-dockerd.deb
sudo systemctl enable --now cri-docker.socket
systemctl status cri-docker.socket
The CRI socket it exposes is the value you'll hand to kubeadm:
unix:///var/run/cri-dockerd.sock
3. Point kubeadm at the cri-dockerd socket
Everything else in the node setup (kernel modules, sysctl, swap off, the pkgs.k8s.io repo) is identical to the containerd post — only the CRI endpoint changes:
sudo kubeadm init \
--cri-socket=unix:///var/run/cri-dockerd.sock \
--pod-network-cidr=10.244.0.0/16
Worker joins must carry the same flag:
sudo kubeadm join <cp>:6443 \
--token <token> --discovery-token-ca-cert-hash sha256:<hash> \
--cri-socket=unix:///var/run/cri-dockerd.sock
4. Verify
kubectl get nodes -o wide
# CONTAINER-RUNTIME now reads docker://<engine-version>
The gotcha nobody warns you about
With cri-dockerd, images pulled by the kubelet and images you docker pull live in the same store, which is convenient — but docker ps will show a lot of Kubernetes-managed containers you didn't start. Use crictl (CRI-aware) for cluster containers and reserve docker for images you manage by hand, so you don't accidentally docker rm something the kubelet owns.
sudo crictl --runtime-endpoint unix:///var/run/cri-dockerd.sock ps
If the node comes up NotReady or pods can't pull, it's nearly always the cgroup-driver mismatch or the socket path — both covered in the Kubernetes pod-startup error hub and the Docker runtime error guides.
Next in the series: dropping the whole node dance and running Kubernetes inside Docker with kind for local development.
Top comments (0)