Technotes for future me

How to run and manage containers using ctr

Basic container management with ctr

One of the main differences between ctr and docker UX is that the former requires doing more things explicitly and doesn’t allow you to take (many) shortcuts. For instance, with docker, you can run a container without explicitly pulling its image first. With ctr though, you’ll have to pull the image (specifying its full name, including the registry and the tag parts) and only then invoke the ctr run command.

Compare the de-facto standard docker run nginx with the following ctr equivalent:

ctr image pull

ctr run hello1

Notice that unlike user-friendly docker run that generates a unique ID for every container, with ctr, you must supply a container ID yourself (hello1 in the above example).

The ctr run command resembles the docker run command but it doesn’t support all the flags you may be used to. For instance, you won’t be able to publish container ports or do something like --restart=always. But it also can do things that docker run can’t,

List containers

Back to basic container operations, you can list existing containers with:

ctr container ls

Inspect container

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

Remove container

Finally, you can remove a container with ctr container remove . Let’s remove the hello1 container we’ve created earlier:

ctr container remove hello1

Containers vs tasks

Interesting that the ctr run command is actually a shortcut! It’s a combination of ctr container create and ctr task start. Let’s explore this behavior:

# Don't forget to pull the image first!
ctr container create nginx1

If you list the containers ctr container ls, the output will be similar to the following:

CONTAINER    IMAGE                              RUNTIME
nginx1     io.containerd.runc.v2

However, checking the running processes with pgrep nginx will return nothing:

pgrep nginx

As you can see, the container is created but no process is running inside it yet.

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

ctr task start --detach nginx1

If you list the tasks now:

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. So, in containerd, a container seems to be a configuration entity that describes the execution environment, while tasks represent the actual processes running inside of containers.

Note that at least with ctr it doesn’t seem to be possible to have multiple tasks running for the same container simultaneously. You can always stop the running task and then start another one for the same container, but you can’t have two tasks running at the same time. In particular, it means that the task management commands we’ll see below accept container IDs as arguments, not task IDs.

Attaching to background tasks

The nginx1 task from the previous section runs in the background because the ctr task start command was used with the --detach flag. To see the stdout and stderr of a running task, you can attach to it with ctr task attach <container-id>. Let’s try attaching to the nginx1 task:

ctr task attach nginx1

The output should 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
2023/05/06 18:48:22 [notice] 1#1: start worker process 30

But be careful, the ctr task attach command will also reconnect the stdin stream and start forwarding signals from the controlling terminal to the task processes, so hitting Ctrl+C might kill the task.

Unfortunately, ctr doesn’t support the Ctrl+P+Q shortcut to detach from a task - it’s solely docker’s feature. There is also no ctr task logs, so you can’t see the stdout/stderr of a task without attaching to it. Neither you can easily see the logs of a stopped task. It’s a lower-level tool, remember?

Executing commands in containers

Much like in Docker, you can execute a command in a running container. Let’s revive the nginx1 task and execute a command inside the Nginx container:

ctr task start --detach nginx1

Here’s how you can get an interactive shell inside the nginx1 container using ctr task exec:

ctr task exec -t --exec-id shell1 nginx1 sh

When you’re done exploring the inside of the container, you can exit the shell ending the shell1 exec session:

You can also 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:

ctr task exec --exec-id curl1 nginx1 curl

The output will be the standard Nginx welcome page:

<!DOCTYPE html>
<title>Welcome to nginx!</title>

Sending signals to tasks

It’s also possible to send signals to tasks, or rather to the processes running inside the tasks. For instance, here is how you can send a SIGTERM signal to the Nginx process, effectively terminating it:

ctr task kill -s 9 nginx1

Interesting that when all container processes are terminated, the task may still exist:

ctr task ls
nginx1    2756    STOPPED

However, the ctr task ps nginx1 command will show that there are no processes running inside the task.

If your only goal for sending a signal is to terminate the task before removal, there might be a faster way to remove a running task - using the ctr task rm with the --force. In any case, let’s clean things up and remove the stopped nginx1 task:

ctr task rm nginx1


Last updated on 20 Jun 2023
Published on 20 Jun 2023
Edit on GitHub