ctr: Using containerd with the Default Command-Line Client

ctr is a command-line client shipped as part of the containerd project. If you have containerd running on a machine, chances are the ctr binary is also present there.

๐Ÿง‘โ€๐ŸŽ“ In this lesson, the playground is a regular Linux server with pre-installed Docker. Since Docker is built on top of containerd, ctr is also available in the playground - try running ctr version in the online terminal on the right.

The ctr client is similar to Docker's eponymous CLI, but the commands and flags often differ from their (typically more user-friendly) docker analogs. Historically, the primary audience of the ctr client was containerd developers themselves testing the daemon. However, ctr can serve as a great exploration means - it operates right on top of the containerd API, and by examining the available commands, you can get a rough idea of what containerd can or cannot do.

ctr is also well-suitable for learning about the capabilities of low-level OCI container runtimes since the ctr + containerd bundle sits much closer to actual containers than docker + dockerd.

Basic image management with ctr

Pulling images with ctr always requires a fully-qualified reference - in other words, you cannot omit the domain or the tag parts (the digest doesn't have to be provided, though). Try the following command to pull the latest nginx image from Docker Hub:

ctr images pull docker.io/library/nginx:latest

Start the playground to activate solution checking.

You can list local images using the following ctr command:

ctr images ls

As we'll see in the next lesson, containerd is often used by higher-level tools to build container images. However, containerd doesn't provide out-of-the-box image building support, so there's no ctr images build command.

Luckily, you can load existing images into containerd using the ctr images import command. Here is how you can build an image with docker build (or any other OCI-compatible image builder) and then import it:

docker build -t iximiuz/test -f - . <<EOF
FROM busybox:latest
CMD echo just a test
EOF

docker save -o iximiuz-test.tar iximiuz/test

ctr images import iximiuz-test.tar

Start the playground to activate solution checking.

Much like in Docker, you can tag local images with the ctr images tag command. For instance:

ctr images tag docker.io/iximiuz/test:latest localhost:5000/iximiuz/test:latest

Start the playground to activate solution checking.

Now you can push the image to a local registry - the playground already has one running on port 5000:

ctr images push localhost:5000/iximiuz/test:latest

Start the playground to activate solution checking.

To remove the image, use the ctr images remove command:

ctr images remove localhost:5000/iximiuz/test:latest

Start the playground to activate solution checking.

Advanced image management with ctr

Sometimes, you may want to inspect the internal structure of an image. With ctr, you can do it using the ctr images export command that saves the image's rootfs to a tarball:

ctr images export /tmp/nginx.tar docker.io/library/nginx:latest

Start the playground to activate solution checking.

When dealing with image layers is not desirable, you can mount an image to a temporary directory on the host and explore its contents as a regular filesystem:

mkdir /tmp/nginx
ctr images mount docker.io/library/nginx:latest /tmp/nginx

Start the playground to activate solution checking.

The file contents of the Nginx image should appear under /tmp/nginx. Try:

ls -l /tmp/nginx/

The result will look like a typical Linux filesystem, and it's not surprising since the Nginx image is based on the Debian Linux distribution:

total 84
drwxr-xr-x 2 root root 4096 May  2 00:00 bin
drwxr-xr-x 2 root root 4096 Apr  2 11:55 boot
drwxr-xr-x 2 root root 4096 May  2 00:00 dev
drwxr-xr-x 1 root root 4096 May  3 19:51 docker-entrypoint.d
-rwxrwxr-x 1 root root 1616 May  3 19:50 docker-entrypoint.sh
drwxr-xr-x 1 root root 4096 May  3 19:51 etc
...

Don't forget to unmount the image when you're done with it ๐Ÿ˜‰

ctr images unmount /tmp/nginx

Start the playground to activate solution checking.

Basic container management with ctr

Having a local image, you can run a container with ctr run <image-ref> <container-id>. For instance, here is how you can run a docker.io/iximiuz/test:latest container from the image we've imported earlier:

ctr run -t docker.io/iximiuz/test:latest test1

Start the playground to activate solution checking.

Notice that unlike user-friendly docker run that generates a unique container ID for you, with ctr, you must supply the unique container ID yourself. The ctr run command supports only the most basic flags: --env, -t,--tty, -d,--detach, --rm, etc. But no port publishing or automatic container restart with --restart=always - these are not supported by containerd out of the box.

Similarly to images, you can list existing containers with:

ctr containers ls

You can also inspect a container with ctr containers info <container-id>:

ctr containers info test1

Finally, you can remove a container with ctr containers remove <container-id>:

ctr containers remove test1

Start the playground to activate solution checking.

Hi there! ๐Ÿ‘‹ I'm Ivan, the author of this course and the founder of iximiuz Labs.

If you've found this platform helpful on your learning journey, please consider supporting its development on Patreon. As a patron, not only will you have my heartfelt thanks, but you'll also enjoy some nice perks!

Advanced container management with ctr

Interesting that the ctr run command is actually a shortcut for ctr container create followed by ctr task start. Let's explore this behavior:

ctr container create -t docker.io/library/nginx:latest nginx1

Start the playground to activate solution checking.

Let's list the created containers:

ctr container ls
CONTAINER    IMAGE                              RUNTIME
...
nginx1       docker.io/library/nginx:latest     io.containerd.runc.v2

...and check the running processes:

pgrep nginx
<empty>

As you can see, the container is created but not running.

To make the Nginx container actually run, you'll need to start a task:

ctr task start -d nginx1  # -d for --detach

Start the playground to activate solution checking.

You can list the running tasks with the following command:

ctr task ls

The output should be similar to the following:

TASK      PID      STATUS    
nginx1    39928    RUNNING

๐Ÿค“ I like this separation of container and task subcommands because it reflects the often forgotten nature of OCI containers. Despite the common belief, containers aren't processes - containers are isolated and restricted execution environments for processes.

To see the stdout/stderr of the running task, you can attach to it with ctr task attach <task-id>. But be careful, this command will reconnect all stdio streams of the task to your terminal, so hitting Ctrl+C will kill the task:

ctr task attach nginx1

The output will be similar to the following:

...
2023/05/06 18:48:22 [notice] 1#1: using the "epoll" event method
2023/05/06 18:48:22 [notice] 1#1: nginx/1.23.4
2023/05/06 18:48:22 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
2023/05/06 18:48:22 [notice] 1#1: OS: Linux 5.10.175
2023/05/06 18:48:22 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/05/06 18:48:22 [notice] 1#1: start worker processes
2023/05/06 18:48:22 [notice] 1#1: start worker process 29
...

Much like in Docker, you can execute a task in an existing container. Open another terminal tab on the right and run the following command to get an interactive shell inside the nginx1 container:

ctr task exec -t --exec-id bash1 nginx1 bash

Or you can execute a single command inside the container without getting an interactive shell. For instance, here's how you can curl the Nginx container from the host (you may need another terminal tab for this):

ctr task exec --exec-id curl1 nginx1 curl 127.0.0.1:80

The output will be the standard Nginx welcome page:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...

Before removing a container, all its tasks must be stopped:

ctr task kill -s 9 nginx1

Alternatively, you can remove running tasks using the --force flag:

ctr task rm -f nginx1

Start the playground to activate solution checking.

Summary

In this lesson, you've learned how to use the ctr command to manage containerd images and containers. One of the key takeaways should probably be that it's doable but not very convenient. As a lower-level container runtime, containerd doesn't provide such familiar to Docker users featurer as image building, port publishing, or automatic container restart on failure. In the next lesson, we'll see how to use the nerdctl command-line client to manage deal with containers and images in a much more user-friendly way.