Containerd is a lightweight, high-performance container runtime that serves as the default container engine for Kubernetes since Docker was deprecated as a direct runtime in Kubernetes 1.24. On RHEL 8, installing containerd from the Docker CE repository gives you a stable, well-supported runtime that integrates cleanly with kubeadm and supports the CRI (Container Runtime Interface) socket Kubernetes requires. This tutorial walks you through installing containerd, configuring it with the correct cgroup driver, joining it to your Kubernetes cluster, and using crictl and nerdctl for container management tasks. Understanding the container runtime layer is fundamental to troubleshooting Kubernetes node issues.
Prerequisites
- RHEL 8 node (fresh or existing Kubernetes worker node)
- Root or sudo access
dnfpackage manager available- Internet access to reach the Docker CE and Kubernetes repositories
- SELinux in enforcing or permissive mode (containerd supports both)
Step 1 — Add the Docker CE Repository and Install containerd
The Docker CE repository is the recommended source for the containerd.io package on RHEL 8. It provides more up-to-date releases than the version in standard RHEL repositories.
# Add the Docker CE repository
dnf config-manager --add-repo
https://download.docker.com/linux/rhel/docker-ce.repo
# Install containerd
dnf install -y containerd.io
# Verify installation
containerd --version
Enable and start the containerd service:
systemctl enable --now containerd
systemctl status containerd
Step 2 — Generate and Edit the Default Configuration
Containerd uses a TOML configuration file at /etc/containerd/config.toml. Generate the default config and then edit two critical settings: enable the CRI plugin and switch to the systemd cgroup driver. Kubernetes requires the systemd cgroup driver to match the one used by the kubelet.
# Generate default config
containerd config default | tee /etc/containerd/config.toml
# Set SystemdCgroup = true in the runc options section
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/'
/etc/containerd/config.toml
# Verify the change
grep 'SystemdCgroup' /etc/containerd/config.toml
The relevant section in /etc/containerd/config.toml should look like this after the edit:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
Restart containerd to apply the configuration:
systemctl restart containerd
systemctl status containerd
Step 3 — Configure Kubernetes to Use containerd
When running kubeadm init or kubeadm join, specify the containerd CRI socket explicitly so kubeadm does not default to Docker or another runtime.
# Initialize the control plane specifying the containerd socket
kubeadm init
--cri-socket unix:///run/containerd/containerd.sock
--pod-network-cidr=10.244.0.0/16
# For worker nodes joining an existing cluster
kubeadm join :6443
--token
--discovery-token-ca-cert-hash sha256:
--cri-socket unix:///run/containerd/containerd.sock
If the kubelet is already running on the node but using a different runtime, update /var/lib/kubelet/kubeadm-flags.env to add --container-runtime-endpoint=unix:///run/containerd/containerd.sock and restart the kubelet:
systemctl restart kubelet
systemctl status kubelet
Step 4 — Verify with crictl
crictl is the CRI-compliant command-line tool for inspecting containers and images managed by containerd (or any CRI runtime). It is the replacement for docker ps and docker images on Kubernetes nodes.
# Configure crictl to use the containerd socket
cat > /etc/crictl.yaml << 'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF
# List running containers (equivalent to docker ps)
crictl ps
# List locally cached images
crictl images
# Pull an image
crictl pull registry.k8s.io/pause:3.9
# Inspect a running container
crictl inspect
# View container logs
crictl logs
Step 5 — Install and Use nerdctl as a Docker-Compatible CLI
nerdctl is a Docker-compatible CLI for containerd that supports the same command syntax as Docker, including nerdctl run, nerdctl build, and nerdctl compose. It is useful for local development and testing on nodes where Docker is not installed.
# Download nerdctl (full bundle includes CNI plugins and BuildKit)
NERDCTL_VERSION=1.7.6
curl -sSL
https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-full-${NERDCTL_VERSION}-linux-amd64.tar.gz
| tar -C /usr/local -xz
# Verify installation
nerdctl version
# Run a container (Docker-compatible syntax)
nerdctl run --rm hello-world
# List containers
nerdctl ps -a
# Build an image from a Dockerfile
nerdctl build -t myapp:latest .
# Push to a registry
nerdctl push myregistry.example.com/myapp:latest
Step 6 — Troubleshoot Common containerd Issues
Use these commands to diagnose containerd problems on RHEL 8 nodes when pods fail to start or images cannot be pulled.
# Check containerd service logs
journalctl -u containerd -f --no-pager | tail -50
# Check the containerd socket exists and is accessible
ls -la /run/containerd/containerd.sock
# Verify kubelet is using the correct runtime endpoint
journalctl -u kubelet --no-pager | grep "container runtime"
# List containerd namespaces (k8s.io namespace is used by Kubernetes)
ctr namespaces list
# List all images in the k8s.io namespace
ctr -n k8s.io images list
# Force remove a stuck container
crictl stop
crictl rm
Conclusion
You have installed containerd on RHEL 8 from the Docker CE repository, generated and configured /etc/containerd/config.toml with the systemd cgroup driver, connected it to Kubernetes via the CRI socket in kubeadm, managed containers using crictl, and set up nerdctl as a Docker-compatible CLI for local container workflows. Containerd provides a stable, Kubernetes-native runtime that is lighter than the full Docker engine while remaining fully compatible with OCI container images.
Next steps: Configure a private container registry mirror in containerd’s config.toml to speed up image pulls, Set up image content trust and signature verification with containerd, and Explore kata-containers as a drop-in containerd runtime for hardware-isolated pods.