Prepare a Node for kubeadm with cri-dockerd
This is one of those CKA tasks with no manifest to copy from the docs. You prepare a plain Linux box so kubeadm can use Docker as the runtime. It is pure system administration, and you have to know the steps cold. Let's walk through it.
This is a CKA Cluster Architecture, Installation & Configuration walkthrough. Every command below is real output from a live cluster, and you can reproduce the whole thing yourself (scripts at the end).
The scenario
Docker is already installed on this node, but kubeadm cannot talk to Docker directly anymore. You install cri-dockerd, the shim that lets the kubelet use Docker as a container runtime, then enable its service, then set the kernel parameters Kubernetes networking depends on and load them without a reboot.
- A Debian node with Docker present, but no CRI shim
- Install the cri-dockerd package with dpkg
- Enable and start the cri-docker service
- Set the required sysctl params and load them now
Why cri-dockerd and these kernel params
Since Kubernetes 1.24 the kubelet only speaks the Container Runtime Interface. Docker does not implement it, so cri-dockerd sits in between: the kubelet connects to the cri-docker socket, and cri-dockerd translates to Docker. On top of that, the kubelet and kube-proxy need bridged traffic to be visible to iptables and need IP forwarding on, which is what the sysctl settings provide.
Confirm the gap
Start by confirming the gap. The cri-dockerd package is not installed, so the kubelet would have nothing to connect to and kubeadm init would fail before it even starts. And this is a Debian box with Docker already present, so we will grab the debian-built package.
$ dpkg -l | grep cri-dockerd || echo 'cri-dockerd: not installed'
cri-dockerd: not installed
$ grep PRETTY_NAME /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
Install the package
Install the package the exam way, with dpkg. Here the .deb is fetched from the cri-dockerd releases; in the exam it is already staged on the box. Run dpkg -i, then confirm dpkg lists it. If dpkg reported missing dependencies you would run apt-get install -f to fix them, but a single static package like this usually needs nothing.
$ wget -q https://github.com/Mirantis/cri-dockerd/releases/download/v0.4.3/cri-dockerd_0.4.3.3-0.debian-bookworm_amd64.deb -O cri-dockerd.deb && ls -lh cri-dockerd.deb
-rw-r--r-- 1 root root 11M Apr 28 09:59 cri-dockerd.deb
$ sudo dpkg -i cri-dockerd.deb
Selecting previously unselected package cri-dockerd.
(Reading database ... 24903 files and directories currently installed.)
Preparing to unpack cri-dockerd.deb ...
Unpacking cri-dockerd (0.4.3~3-0~debian-bookworm) ...
Setting up cri-dockerd (0.4.3~3-0~debian-bookworm) ...
Created symlink '/etc/systemd/system/multi-user.target.wants/cri-docker.service' → '/usr/lib/systemd/system/cri-docker.service'.
Created symlink '/etc/systemd/system/sockets.target.wants/cri-docker.socket' → '/usr/lib/systemd/system/cri-docker.socket'.
/usr/sbin/policy-rc.d returned 101, not running 'start cri-docker.service cri-docker.socket'
$ dpkg -l | grep cri-dockerd
ii cri-dockerd 0.4.3~3-0~debian-bookworm amd64 cri-dockerd is a lightweight implementation of the CRI specification which talks to docker.
Enable + start the service
Now enable and start cri-docker. The kubelet connects through the socket unit, so enable --now on cri-docker.socket, and enable the service so it survives a reboot. The socket comes up listening immediately; the service itself starts on demand the first time the kubelet connects.
$ sudo systemctl enable --now cri-docker.socket
$ sudo systemctl enable cri-docker.service
$ systemctl is-enabled cri-docker.socket cri-docker.service
enabled
enabled
$ systemctl status cri-docker.socket --no-pager
● cri-docker.socket - CRI Docker Socket for the API
Loaded: loaded (/usr/lib/systemd/system/cri-docker.socket; enabled; preset: enabled)
Active: active (listening) since Thu 2026-06-25 17:59:47 UTC; 447ms ago
Invocation: f4095949423e40418fecae56ada9ccc1
Triggers: ● cri-docker.service
Listen: /run/cri-dockerd.sock (Stream)
Tasks: 0 (limit: 19008)
Memory: 0B (peak: 256K)
CPU: 693us
CGroup: /system.slice/cri-docker.socket
Jun 25 17:59:47 cka-scenario4-control-plane systemd[1]: Starting cri-docker.socket - CRI Docker Socket for the API...
Jun 25 17:59:47 cka-scenario4-control-plane systemd[1]: Listening on cri-docker.socket - CRI Docker Socket for the API.
Configure kernel params
Next, the kernel parameters. The bridge keys only exist once the br_netfilter module is loaded, and nf_conntrack_max needs the nf_conntrack module, so load both and record them in modules-load.d so they come back after a reboot. Then drop the four parameters into a dedicated file under sysctl.d, which is the persistent, best-practice place for them.
$ sudo modprobe br_netfilter nf_conntrack
$ cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
nf_conntrack
EOF
br_netfilter
nf_conntrack
$ cat <<'EOF' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.netfilter.nf_conntrack_max = 131072
EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.netfilter.nf_conntrack_max = 131072
$ cat /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.netfilter.nf_conntrack_max = 131072
Load + verify
Apply the settings without rebooting and verify. sysctl --system re-reads every file under sysctl.d, so the new values take effect right now. Then read the keys back: bridged traffic is now visible to iptables on IPv4 and IPv6, and forwarding is on. The conntrack maximum is written into the same file, so it is in place for the node too. The box is ready for kubeadm.
$ sudo sysctl --system
* Applying /etc/sysctl.d/k8s.conf ...
$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
Exam tips
A few things that trip people up. Use the package they tell you to and install it with dpkg -i, not apt. The kubelet talks to the socket, so enable the socket, not just the service. The bridge sysctls silently fail to apply if br_netfilter is not loaded, so modprobe it first. And always finish with sysctl --system and read the values back, because a config file that is never loaded scores zero.
- Install the given .deb with dpkg -i (apt-get -f install only if deps complain)
- Enable cri-docker.socket, not just the service, the kubelet uses the socket
- modprobe br_netfilter first or the bridge keys never appear
- Finish with sysctl --system and verify each value
Recap
- dpkg -i the cri-dockerd package
- enable --now cri-docker.socket + enable the service
- Persist modules + four sysctl params, then sysctl --system
- Subscribe + dev.to writeup
Reproduce this yourself
The entire scenario is scripted on a throwaway kind cluster: https://github.com/The-Cyber-Sidekick/TCS_CKA_2026_Exam_Scenarios
git clone https://github.com/The-Cyber-Sidekick/TCS_CKA_2026_Exam_Scenarios.git
cd TCS_CKA_2026_Exam_Scenarios/learning/scenarios/scenario4-cri-dockerd-system-prep
./setup.sh # creates the cluster AND arms the scenario
# solve it by hand, or:
./solution.sh # apply the answer key and verify
If this helped, subscribe to The Cyber SideKick on YouTube for more CKA drills, and grab the newsletter at https://thecybersidekick.beehiiv.com.
Top comments (0)