Introduction
I compared 3 container runtimes previously, which included runsc from gVisor. Since the default is runc, that is the default in Docker Desktop as well. The Kata runtime could not be tested in Docker Desktop, since that would require nested virtualization in the VM of Docker Desktop, so runsc is the only runtime from that comparison we could try in Docker Desktop which isn't already in it.
The only question is how we could copy the runtime binary into Docker Desktop. This could also be asked in general related to any binary, since the root filesystem of Docker Desktop is read-only, but the runtime is executed by Docker so copying that would actually make sense.
Table of contents
- Download runsc into Docker Desktop
- Configure the Docker daemon to use the runtime
- Download and install runsc in one step
- Testing runsc after the installation
- Reloading the internal daemon config to support Windows
- Possible error messages
Download runsc into Docker Desktop
If a runtime can be downloaded directly without using any package manager, you can download it to a location which can be read by the Docker daemon. There is one thing that we know the Docker daemon can read, and we can write, and that is a local volume. So we will need a container that downloads runsc to a local volume.
First we will need to check the official documentation for an installation guide. Despite that the documentation mentions installing specific releases as well, I couldn't figure it out and the link they provide to the releases on GitHub shows that there is no release at all, so we will install the latest version.
I used the script from the documentation, except that I removed the parenthesis and I also needed to remove sudo
from the beginning of the last line. We don't need it, since we will use the root user in the container. I will use the script directly in a compose file, so I had to escape all the dollar characters with a second dollar character. This is the compose file:
services:
runsc-installer:
image: alpine:3.20
volumes:
- type: volume
source: data
target: /usr/local/bin
command:
- sh
- -c
- |
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/$${ARCH}
wget $${URL}/runsc $${URL}/runsc.sha512 \
$${URL}/containerd-shim-runsc-v1 $${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 \
-c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
mv runsc containerd-shim-runsc-v1 /usr/local/bin
volumes:
data:
name: runsc-runtime-binaries
The chance of you using runsc-runtime-binaries
as a volume for something else is very low. But if you do have it, make sure you use a different volume name.
Place the file anywhere as compose.yml
and run
docker compose run --rm runsc-installer
You will see the progress which is something like this:
Connecting to storage.googleapis.com (172.217.19.123:443)
saving to 'runsc'
runsc 100% |****************************************************************| 60.2M 0:00:00 ETA
'runsc' saved
Connecting to storage.googleapis.com (172.217.19.123:443)
saving to 'runsc.sha512'
runsc.sha512 100% |****************************************************************| 136 0:00:00 ETA
'runsc.sha512' saved
Connecting to storage.googleapis.com (172.217.19.123:443)
saving to 'containerd-shim-runsc-v1'
containerd-shim-runs 100% |****************************************************************| 27.7M 0:00:00 ETA
'containerd-shim-runsc-v1' saved
Connecting to storage.googleapis.com (172.217.19.123:443)
saving to 'containerd-shim-runsc-v1.sha512'
containerd-shim-runs 100% |****************************************************************| 155 0:00:00 ETA
'containerd-shim-runsc-v1.sha512' saved
runsc: OK
containerd-shim-runsc-v1: OK
Whenever a new version comes out, you can run the command again, and it will replace the old files.
Configure the Docker daemon to use the runtime
As I also mentioned in Everything about Docker volumes, you can inspect a volume and see the mount point
docker volume inspect runsc-runtime-binaries
This is my output:
[
{
"CreatedAt": "2024-10-28T17:21:24Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "03-gvisor-in-docker-desktop-data",
"com.docker.compose.version": "2.29.7",
"com.docker.compose.volume": "data"
},
"Mountpoint": "/var/lib/docker/volumes/runsc-runtime-binaries/_data",
"Name": "runsc-runtime-binaries",
"Options": null,
"Scope": "local"
}
]
Normally a mount point would be where you mount something not from where you mounted it, but I guess it is called mount point because of what I described in the linked tutorial, since unless you are using the default local volumes, this is where the data could be mounted to. Now that we know that, the following script can be used to add the runtime to the daemon config:
#!/usr/bin/env bash
volume="runsc-runtime-binaries"
volume_path="/var/lib/docker/volumes/$volume/_data"
docker run -it --rm \
--mount "type=bind,source=$HOME/.docker,target=/etc/docker" \
--mount "type=volume,source=$volume,target=$volume_path" \
ubuntu "$volume_path/runsc" install
This script also mounts $HOME/.docker
to /etc/docker
in the container, since it contains the daemon.json
and runsc install
will add the parameters to /etc/docker/daemon.json
by default and back up the original file as daemon.json~
. If you save the script as runsc-install.sh
, this is how you could run it:
chmod +x ./runsc-install.sh
./runsc-install.sh
This script would work only on Linux and macOS, and we would have other problems as well, so before doing too much this way, let's jump to the next section and introduce Docker Compose.
Download and install runsc in one step
If we want to make the installer simpler, we can add the "install" command to the compose file. This means that we have to mount $HOME/.docker
in the compose file. We should also remove the last line of the original script in the compose file, because we don't want to move the binaries to /usr/local/bin
anymore, since runsc install
will need to run from a different folder, and we need only one mount for the volume. So we change the mount definition of the volume too and we also want a platform independent way to refer to our home folder so we replace $HOME
with the tilde character (~
), which actually came as a surprise to me, but it works in the compose file even on Windows. And finally, we run the installer. So these are the last three lines:
dest="/var/lib/docker/volumes/${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}/_data/"
mv runsc containerd-shim-runsc-v1 $$dest
$$dest/runsc install
Notice that $dest
is escaped with a second dollar character but $RUNSC_VOLUME_NAME
isn't. This is because the volume name will be a compose parameter with a default value so it has to be interpreted by compose. This is the full compose file:
services:
runsc-installer:
image: alpine:3.20
volumes:
- type: volume
source: data
target: /var/lib/docker/volumes/${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}/_data/
- type: bind
source: ~/.docker
target: /etc/docker
command:
- sh
- -c
- |
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/$${ARCH}
wget $${URL}/runsc $${URL}/runsc.sha512 \
$${URL}/containerd-shim-runsc-v1 $${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 \
-c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
dest="/var/lib/docker/volumes/${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}/_data/"
mv runsc containerd-shim-runsc-v1 $$dest
$$dest/runsc install
volumes:
data:
name: ${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}
This way you can even change the volume name. and run the compose project like this on Linux and macOS (or on Windows, with WSL2 integration enabled, and running docker commands in your WSL distribution):
RUNSC_VOLUME_NAME="runsc-bin" docker compose run --rm runsc-installer
My previous examples used docker compose run
, but you can use docker compose up
as well. Then the log lines will be prefixed with the container name and the container will be kept after running it.
Testing runsc after the installation
Before you test the runtime, don't change any Docker Desktop configuration, because that will reset your client config on the host, since the VM still doesn't know about it. In order to avoid resetting the config file, "Quit Docker Desktop" and then start it again. DO NOT just "Restart" it in the menu, because that will just restart services in the VM without updating the config.
When the config is ready and Docker Desktop is started again, you can run
docker run --rm --runtime runsc ubuntu uname -a
If everything works, you will see something like this:
Linux 73309f6efa2c 4.4.0 #1 SMP Sun Jan 10 15:06:54 PST 2016 aarch64 aarch64 aarch64 GNU/Linux
For more testing ideas, check out my blog post about the comparison of runtimes.
Reloading the internal daemon config to support Windows
I already mentioned some ways to support Windows, but there is one more problem. When using the WSL2 backend, Docker Desktop on Windows supports Nvidia GPUs, because WSL2 supports it too. Unfortunately, it seems that the Nvidia runtime configuration currently completely overrides the "runtimes" section in the daemon config instead of adding itself to the existing "runtimes" section.
That means, even if we add runsc in the daemon config on the host, it will not be usable You will see it even in Docker Desktop's graphical interface, but it will not actually be the one that is used by the Docker daemon. So I will show a workaround I came up with, until it is fixed in Docker Desktop if it is ever considered a bug to be fixed. Let's not forget that there is nothing to indicate that using a custom runtime is supported in Docker Desktop. Just because we can do something, it doesn't mean we will always be able to do it if it is not supported. So maybe it will be fixed in the future, but it is also possible, that nothing that I described in this blogpost will work in the future. With that in mind, here is the workaround:
- We have to find the config file used by the docker daemon in the virtual machine
- We have to mount it from the VM into our installer container
- We have to find a way to pass a custom config path to the
runsc install
command and automatically update the internal config file - After updating the internal config file, we have to reload it, which means, we have to send a HUP signal to the dockerd process.
- In order to be able to send signals to the dockerd process, we need to use the process namespace of the host.
The first can be done by running the following command:
docker run --rm -it --pid host alpine:3.20 sh -c 'ps aux | grep dockerd | grep -v grep'
This should show something like the following:
308 root 0:45 /usr/local/bin/dockerd --config-file /run/config/docker/daemon.json --containerd /run/containerd/containerd.sock --pidfile /run/desktop/docker.pid --swarm-default-advertise-addr=192.168.65.3 --host-gateway-ip 192.168.65.254
So we know, the config is at /run/config/docker/daemon.json
. Let's check the content by running the following command on Windows
docker run -v /run/config/docker/daemon.json:/daemon.json alpine:3.20 cat /daemon.json
The output is something like this:
{"builder":{"gc":{"defaultKeepStorage":"20GB","enabled":true}},"experimental":true,"features":{"cone-registries":["hubproxy.docker.internal:5555"],"mtu":1500,"runtimes":{"nvidia":{"path":"nvidia-contain-driver":"overlayfs","userland-proxy":false}
This indeed shows the nvidia container runtime.
We could also download "runsc" to somewhere or use the one on the already created volume to run runsc install -help
, or we can check the source code too: https://github.com/google/gvisor/blob/745828301c936ddd1dac49b2611bdf1a4477f9ab/runsc/cmd/install.go#L60
This shows we have a config_file
option. Great. Now we need to notify the dockerd process that it should reload the daemon config, which requires some Linux experience, but the pkill command can help us like this:
pkill -HUP dockerd
This will work only if we add pid: host
to the installer service.
Note that this will notify all dockerd processes, so if you have Docker in Docker, it will reload all Docker daemon's configuration. It shouldn't be a problem unless you broke the config of any docker daemon. The new compose file is the following now:
services:
runsc-installer:
image: alpine:3.20
volumes:
- type: volume
source: data
target: /var/lib/docker/volumes/${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}/_data/
- type: bind
source: ~/.docker
target: /etc/docker
- type: bind
source: /run/config/docker/daemon.json
target: /run/config/docker/daemon.json
pid: host
command:
- sh
- -c
- |
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/$${ARCH}
wget $${URL}/runsc $${URL}/runsc.sha512 \
$${URL}/containerd-shim-runsc-v1 $${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 \
-c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
dest="/var/lib/docker/volumes/${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}/_data/"
mv runsc containerd-shim-runsc-v1 $$dest
$$dest/runsc install
$$dest/runsc install -config_file /run/config/docker/daemon.json
pkill -HUP dockerd
volumes:
data:
name: ${RUNSC_VOLUME_NAME:-runsc-runtime-binaries}
Using this compose file, you can even test runsc on Windows, but you will have to run the installer every single time you restart Docker Desktop.
Possible error messages
If something goes wrong and runsc cannot be found, you can see an error message like below, which I got when I accidentally wrote "volume" instead of "volumes" in the script, so Docker tried to find the runtime using an incorrect path.
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: unable to retrieve OCI runtime error (open /var/run/desktop-containerd/daemon/io.containerd.runtime.v2.task/moby/3a01e11f99939f9fd85151e2ae97f15d5f39ff94e835187357b82807147dcc52/log.json: no such file or directory): fork/exec /var/lib/docker/volume/runsc-runtime-binaries/_data/runsc: no such file or directory: <nil>: unknown.
Without the automatic reloading of the internal daemon config, if you try the runtime too early before activating it in the VM, for example because your config was reset to the previous one after you changed something in Docker Desktop before stopping it, you could get an error about the invalid runtime name:
docker: Error response from daemon: unknown or invalid runtime name: runsc.
See 'docker run --help'.
Conclusion
Changing the runtime in Docker Desktop is not the first thing that Docker Desktop users think. It is probably not even in the top 10, but sometimes, it can be useful if you have only Docker Desktop at the moment. So this post was basically just a way to know more about Docker Desktop, Docker Compose, and how we can change the daemon configuration manually, or automatically.
If you break the daemon configuration, you can fix it, but you have to quit Docker Desktop and start it again.
You could also learn a little bit about volumes, but if you need more, you can read the already mentioned Everything about Docker volumes
And finally, it is always good to understand general Linux commands even when we use Docker Desktop on Windows, but we want to run Linux containers. It helps us to troubleshoot and do things that are not officially supported yet, but can be done at the moment.
Docker Desktop is not Docker, as I pointed it out in "You run containers, not dockers - Discussing Docker variants, components and versioning", which means there are some things that you can do with Docker Desktop but not with Docker CE, or you can do something with Docker CE but not with Docker Desktop. I still like to find the limits of both so we can enjoy as many features of both as possible.
Top comments (0)