Take your first steps with containerd and run your docker images as usual
In December 2020, the Kubernetes project has deprecated docker. Through this lecture, we are going to have a first look at the docker
history to understand the reasons for this deprecation. This will bring us next to discover containerd
and figure out its advantages.
We will practice containerd
by installing it on a Raspberry Pi. This target platform is a good scenario to dig into the different components needed to use containerd
. There are no available binaries so we will build them from the sources. Once installed, we will see how to interact with it using ctr
command and what there is possible to do.
The Docker Software History
Docker software was created in 2013 and contributed to making containerization famous and accessible. However, people are often confusing docker
and containerization.
Docker β containers
The containerization in Linux was possible with these two Linux kernel features:
- Linux kernel namespace defines the boundaries of a process awareness of what else is running around it.
- Control groups (cgroups) are a kernel feature that manages and isolates the resources (CPU, memory) affected by a process.
Linux Containers (LXC) was released in 2008 and relies on both previous kernel features. It provides an API to easily create and manage container application systems.
In its beginning, docker
was using the LXC stack to isolate application resources. The docker
prowess was to create a standard software unit where the user can define images and launch them easily in containers.
Software architecture evolution
Based on LXC, docker
was really dependent on its development. To remediate that, docker
started to implement libcontainers to launch containers without LXC.
In parallel at that time, there were many criticisms made against the monolithic architecture of docker
containing the CLI and the daemon. Having taken into account these feedbacks docker
started cutting out the daemon and the CLI in docker
.
In 2015 the Open Container Initiative (OCI) was founded to create an application container standard. Docker company has been a major key player in its definition. Two standards emerged from this initiative which constitutes the OCI model :
- runtime-spec defining how to run a container :
- image-spec defining the image creation :
Docker company donated libcontainers
to the OCI. It is now a part of the runc project which is the OCI runtime. Nowadays, docker
is no longer the monolithic block it once was. It uses containerd
and runc
to manage the run of the containers :

Containerd
containerd
was born out of docker
and was included from the 1.11 release :
It is a runtime implementing the Container Runtime Interface (CRI) and presents interesting assets for hosted systems :
βcontainerd is an industry-standard container runtime with an emphasis on simplicity, robustness and portability. It is available as a daemon for Linux and Windows, which can manage the complete container lifecycle of its host system: image transfer and storage, container execution and supervision, low-level storage and network attachments, etc.β From https://github.com/containerd/containerd
Docker and Kubernetes
Before the 1.20 release, kubernetes
was using docker
to communicate with the containers. docker
itself calls containerd to manage the containers :

From v1.20 kubernetes
deprecates docker
in favor of runtimes using the Container Runtime Interface (CRI) such as containerd
and cri-o
. You will receive warnings if you still use docker
in this release. The change will be effective in the v1.22 release. The previous diagram looks like that now:

In spite of this kubernetes
change, you can continue to use Docker in your CI/CD or for development purposes. docker
brings a lot of user-experience features to make the interaction easier. Nevertheless, a container orchestrator like kubernetes
needs only a container runtime to manage containers. Dealing with an extra layer can bring more complexity that does not create more value.
Containerd on a Raspberry Pi
Installation
I use a Raspberry Pi with armv6l processor architecture and Raspbian OS. You should check your processor architecture using uname
and adapt the source code to your architecture.
We ensure the system is up-to-date:
$ sudo apt update
$ sudo apt full-upgrade
$ sudo rpi-update
We need some build tools to compile sources :
$ sudo apt install autoconf automake libtool curl unzip gcc make
We also need a recent golang
version installed :
$ wget https://golang.org/dl/go1.15.8.linux-armv6l.tar.gz
$ sudo tar -C /usr/local -xzf go1.15.8.linux-armv6l.tar.gz
$ echo 'PATH="$PATH:/usr/local/go/bin"' | tee -a $HOME/.profile
$ echo "export GOPATH=$(go env GOPATH)" | tee -a $HOME/.profile
$ source $HOME/.profile
$ go version
# version should be 1.15.8
Install libseccomp
packages for seccomp support:
$ sudo apt install libseccomp2 libseccomp-dev
Install runc
:
$ go get -v github.com/opencontainers/runc
$ cd $GOPATH/src/github.com/opencontainers/runc
$ make -j`nproc`
$ sudo env PATH="$PATH" make install
We need btrfs
packages to prepare protobuf
installation:
sudo apt install btrfs-progs libbtrfs-dev
We download protobuf
sources and build them:
$ wget https://github.com/protocolbuffers/protobuf/archive/v3.14.0.tar.gz -O protobuf_v3.14.0.tar.gz
$ tar xzf protobuf_v3.14.0.tar.gz
$ cd protobuf-3.14.0/
$ ./autogen.sh
$ ./configure
$ make -j`nproc`
$ make -l2 -j`nproc` check
$ sudo make install
We download containerd
sources and build them:
$ go get -v github.com/containerd/containerd
$ cd $GOPATH/src/github.com/containerd/containerd
$ make -j`nproc`
$ sudo env PATH="$PATH" make install
The containerd
build is finished we can check the binary:
$ containerd --version
containerd github.com/containerd/containerd v1.5.0-beta.1 cfa842c278694860a7e32917066f4a24978f80d0
We deploy the provided systemd
service file to run containerd
as a daemon:
cd $GOPATH/src/github.com/containerd/containerd
sudo cp containerd.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable containerd.service
sudo systemctl start containerd.service
Interact with containerd
We are going to use ctr
command to interact with containerd
. It was included in the containerd
installation:
NAME:
ctr -
__
_____/ /______
/ ___/ __/ ___/
/ /__/ /_/ /
\___/\__/_/
containerd CLI
USAGE:
ctr [global options] command [command options] [arguments...]
VERSION:
v1.5.0-beta.1
DESCRIPTION:
ctr is an unsupported debug and administrative client for interacting
with the containerd daemon. Because it is unsupported, the commands,
options, and operations are not guaranteed to be backward compatible or
stable from release to release of the containerd project.
COMMANDS:
plugins, plugin provides information about containerd plugins
version print the client and server versions
containers, c, container manage containers
content manage content
events, event display containerd events
images, image, i manage images
leases manage leases
namespaces, namespace, ns manage namespaces
pprof provide golang pprof outputs for containerd
run run a container
snapshots, snapshot manage snapshots
tasks, t, task manage tasks
install install a new package
oci OCI tools
shim interact with a shim directly
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug enable debug output in logs
--address value, -a value address for containerd's GRPC server (default: "/run/containerd/containerd.sock") [$CONTAINERD_ADDRESS]
--timeout value total timeout for ctr commands (default: 0s)
--connect-timeout value timeout for connecting to containerd (default: 0s)
--namespace value, -n value namespace to use with commands (default: "default") [$CONTAINERD_NAMESPACE]
--help, -h show help
--version, -v print the version
containerd
can pull images from a container registry and it supports tags. We usectr
to pull the nginx
image:
$ sudo ctr image pull docker.io/library/nginx:latest
Using ctr
we can have the list of images. We can ensure the nginx
image has been pulled:
$ sudo ctr image ls
$ sudo ctr image ls -q
docker.io/library/nginx:latest
We have the nginx
image and we can now run a container using it:
$ sudo ctr container create docker.io/library/nginx:latest webdemo
Is the container running? Letβs confirm it by listing the containers:
$ sudo ctr container list
CONTAINER IMAGE RUNTIME
webdemo docker.io/library/nginx:latest io.containerd.runc.v2
If we delete the image the container is still running :
$ sudo ctr image remove docker.io/library/nginx:latest
docker.io/library/nginx:latest
$ sudo ctr image ls -q
$ sudo ctr container list
CONTAINER IMAGE RUNTIME
webdemo docker.io/library/nginx:latest io.containerd.runc.v2
Now we can remove the container :
sudo ctr container delete webdemo
Conclusion
We had an overview of docker
history. We have seen it was monolithic software in the beginning. Docker has contributed to defining container standards in the OCI. Resulting in that, docker
architecture has changed.
Initially, kubernetes
was using docker
but decided in the 1.20 release to deprecate it. We have seen that docker
communicates with containerd
and runc
to run containers. Its added value is to provide a great user-experience to manage images and resources for developers.
In the case of a distributed system like kubernetes
, it can directly call the container runtime. An extra layer is not needed and can create complexity and more calls across the software layers.
Finally, we practiced containerd
by installing and configuring it on a Raspberry Pi. It is a good target to test containerd
because it has limited resources and most of the time you donβt build images on it. You just want to pull images and run them into containers. We have observed we can pull docker
images and launch containers as usual with containerd
.
Resources

