How to Install containerd as a Container Runtime on RHEL 7
containerd is a high-performance, industry-standard container runtime that serves as the foundation beneath both Docker and Kubernetes. When Kubernetes deprecated the Dockershim in version 1.20 and removed it in version 1.24, containerd became the most widely adopted direct container runtime for Kubernetes clusters. Installing containerd directly without Docker as an intermediary reduces overhead, simplifies the software stack, and aligns with how modern managed Kubernetes services like Amazon EKS and Google GKE operate internally. On RHEL 7, where stability and a minimal attack surface are organizational priorities, using containerd as the sole container runtime is the preferred approach for new Kubernetes deployments. This tutorial covers downloading containerd from GitHub releases, configuring it as a systemd service, customizing /etc/containerd/config.toml, using nerdctl as a Docker-compatible CLI, managing containers through containerd namespaces, and integrating the runtime with kubeadm.
Prerequisites
- RHEL 7.x system with root or sudo access
- Active Red Hat subscription or EPEL repository configured
curl,tar, andsystemctlavailable on the system- Internet access from the server to download containerd and CNI plugins
- SELinux and firewalld enabled (we will configure them appropriately)
- At least 2 GB disk space available under
/var/lib/containerd
Step 1: Install Required Dependencies
containerd requires runc as its low-level container executor and CNI plugins for container networking. Start by installing system libraries:
yum install -y libseccomp libseccomp-devel
device-mapper-persistent-data lvm2
Download and install the runc binary from its official GitHub releases. runc is the OCI-compliant container runtime that containerd delegates to for actually starting processes:
curl -fsSL -o /usr/local/sbin/runc
https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64
chmod 755 /usr/local/sbin/runc
runc --version
Install CNI plugins, which are required for pod-to-pod networking inside Kubernetes:
mkdir -p /opt/cni/bin
curl -fsSL
https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz
| tar -xz -C /opt/cni/bin
ls /opt/cni/bin
Step 2: Download and Install containerd Binaries
Download the containerd binary tarball directly from the official GitHub release page. Using the tarball avoids package manager conflicts with any existing Docker installation:
CONTAINERD_VERSION="1.7.14"
curl -fsSL
https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz
-o /tmp/containerd.tar.gz
tar -xvzf /tmp/containerd.tar.gz -C /usr/local
# Verify the binaries were installed correctly
ls -la /usr/local/bin/containerd*
The tarball extracts into /usr/local/bin/ and includes containerd, containerd-shim-runc-v1, containerd-shim-runc-v2, and the low-level ctr client binary.
Step 3: Install containerd as a systemd Service
containerd ships with an official systemd unit file. Download and install it so containerd starts automatically on boot:
curl -fsSL
https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
-o /etc/systemd/system/containerd.service
systemctl daemon-reload
systemctl enable containerd
systemctl start containerd
systemctl status containerd
Verify that the containerd UNIX socket is present, which confirms the daemon is running and accepting connections:
ls -la /run/containerd/containerd.sock
Step 4: Generate and Configure /etc/containerd/config.toml
containerd’s behavior is fully controlled by /etc/containerd/config.toml. Generate the default configuration as a starting point:
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
The most critical setting for Kubernetes compatibility is enabling systemd cgroup management. Locate the runc options section and verify the current value:
grep -n "SystemdCgroup" /etc/containerd/config.toml
Set SystemdCgroup = true to enable cgroup v2 compatibility, which is required when using systemd as the init system on RHEL 7:
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/'
/etc/containerd/config.toml
# Confirm the change was applied
grep "SystemdCgroup" /etc/containerd/config.toml
Configure the Pause (Sandbox) Image
The pause container image is used by Kubernetes to hold the network namespace for each pod. Ensure it matches the version expected by your Kubernetes release:
grep "sandbox_image" /etc/containerd/config.toml
Update to the correct pause image version if needed:
sed -i 's|sandbox_image = "registry.k8s.io/pause:3.8"|sandbox_image = "registry.k8s.io/pause:3.9"|'
/etc/containerd/config.toml
Restart containerd to apply all configuration changes:
systemctl restart containerd
systemctl status containerd
Step 5: Install nerdctl as a Docker-Compatible CLI
The built-in ctr tool is intentionally low-level and lacks many features developers expect. nerdctl provides a Docker-compatible command-line interface for containerd, supporting the same syntax for run, pull, push, build, and compose operations:
NERDCTL_VERSION="1.7.4"
curl -fsSL
https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz
| tar -xz -C /usr/local/bin
nerdctl version
Test basic container operations with nerdctl:
# Pull an image
nerdctl pull nginx:stable
# Run a container in the background
nerdctl run --rm -d -p 8080:80 --name testnginx nginx:stable
# List running containers
nerdctl ps
# Check the container responds
curl http://localhost:8080/
# Stop and remove it
nerdctl stop testnginx
Step 6: Understand containerd Namespaces
containerd uses an internal namespace model to isolate resources between different consumers. Kubernetes uses the k8s.io namespace, while nerdctl and interactive users work in the default namespace. This separation ensures that docker ps-style commands do not accidentally list or interfere with Kubernetes-managed containers.
# List all containerd namespaces
ctr namespaces list
# List images visible to Kubernetes
ctr -n k8s.io images list
# List running containers in the Kubernetes namespace
ctr -n k8s.io containers list
# Use nerdctl with an explicit namespace
nerdctl --namespace k8s.io ps -a
When debugging a Kubernetes pod issue at the containerd level, always specify -n k8s.io to see the resources Kubernetes has created. The default namespace will be empty for pods started by Kubernetes.
Step 7: Configure Kernel Modules and Sysctl for Kubernetes
Before integrating containerd with kubeadm, load the required kernel modules and configure network settings on RHEL 7:
cat > /etc/modules-load.d/containerd.conf <<EOF
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat > /etc/sysctl.d/99-kubernetes.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
Confirm both modules are loaded successfully:
lsmod | grep overlay
lsmod | grep br_netfilter
Step 8: Integrate containerd with kubeadm
When running kubeadm init or kubeadm join, specify the containerd CRI socket explicitly to ensure kubeadm does not accidentally detect another runtime:
# Initialize a Kubernetes control plane with containerd
kubeadm init
--pod-network-cidr=10.244.0.0/16
--cri-socket unix:///run/containerd/containerd.sock
For joining worker nodes to an existing cluster:
kubeadm join 192.168.1.100:6443
--token abcdef.0123456789abcdef
--discovery-token-ca-cert-hash sha256:<your-hash>
--cri-socket unix:///run/containerd/containerd.sock
After the cluster is initialized, verify that Kubernetes correctly identifies containerd as the runtime:
kubectl get nodes -o wide
# The CONTAINER-RUNTIME column should read: containerd://1.7.14
Step 9: Open Required Firewall Ports on RHEL 7
Use firewalld to open all ports required for Kubernetes cluster communication on RHEL 7:
# Control plane node ports
firewall-cmd --permanent --add-port=6443/tcp # Kubernetes API server
firewall-cmd --permanent --add-port=2379-2380/tcp # etcd server
firewall-cmd --permanent --add-port=10250/tcp # kubelet API
firewall-cmd --permanent --add-port=10251/tcp # kube-scheduler
firewall-cmd --permanent --add-port=10252/tcp # kube-controller-manager
# Worker node ports
firewall-cmd --permanent --add-port=10250/tcp # kubelet API
firewall-cmd --permanent --add-port=30000-32767/tcp # NodePort services
firewall-cmd --reload
firewall-cmd --list-ports
Conclusion
You have installed containerd on RHEL 7 by downloading the binary tarball from official GitHub releases, installing runc and CNI plugins as dependencies, registering containerd as a systemd service that starts automatically on boot, generating and customizing /etc/containerd/config.toml with systemd cgroup support and the correct pause image, installing nerdctl as a user-friendly Docker-compatible CLI, explored containerd’s internal namespace model for debugging Kubernetes workloads, and configured the CRI socket endpoint for kubeadm cluster initialization. containerd’s lean architecture, active CNCF maintenance, and native Kubernetes integration make it the optimal container runtime for RHEL 7 production environments. For further optimization, configure a local container registry mirror in config.toml under the plugins."io.containerd.grpc.v1.cri".registry section to reduce image pull latency, and monitor containerd performance using its built-in Prometheus metrics endpoint at localhost:1338/metrics on each node.