Author: Tobit Flatscher (August 2021 - April 2022)
Running user interfaces from inside a Docker might not be its intended usage but as of now there are several options available. The problem with all of them is that they are not portable to other operating systems. On the the ROS Wiki the different options are discussed in more detail. The options that I am aware of are the following:
- In Linux there are several ways of connecting a containers output to a host's X server resulting in an output which is indistinguishable from a program running on the host directly. This is the approach chosen for this tutorial. It is quite portable but requires additional steps for graphics cards running with nVidia hardware acceleration rather than the Linux Nouveau display driver.
- Other common approaches are less elegant and often use some sort of Virtual Network Computing (VNC) which streams the entire desktop to the host over a dedicated window similar to connecting virtually to a remote machine.
As pointed out before this guide uses the Ubuntu X-Server for outputting dialogs from a Docker. The Docker-Compose file with and without Nvidia hardware acceleration look differently and Nvidia hardware acceleration requires a few additional setup steps. These differences are briefly outlined in the sections below. It is worth mentioning that just having an Nvidia card does not necessitate the Nvidia setup but nstead what matters is the driver used for the graphics card. If you are using a Nouveau driver for an Nvidia card then a Docker-Compose file written for hardware acceleration won't work and instead you will have to turn to the Docker-Compose file without it. And vice versa, if your card is managed by the Nvidia driver then the first approach won't work for you. This fact is in particular important for the PREEMPT_RT
patch: You won't be able to use your Nvidia driver when running the patch. Your operating system might mistakenly tell you that it is using the Nvidia driver but it might not. It is therefore important to check the output of $ nvidia-smi
. If it outputs a managed graphics card, then you will have to go for the second approach with hardware acceleration. If it does not output anything or is not even installed go for the Nouveau driver setup.
In any case before being able to stream to the X-Server on the host system you will have to run the following command inside the host system:
$ xhost +local:root
where root
corresponds to the user-name of the user used inside the Docker container. This command has to be repeated after each restart.
As pointed out in the second before this type of setup applies to any card that is not managed by an Nvidia driver, even if the card is an Nvidia card.
In such a case it is sufficient to share the following few folders with the host system. The docker-compose.yml
might look as follows:
version: "3.9"
services:
some_name:
build:
context: .
dockerfile: Dockerfile
tty: true
environment:
- DISPLAY=${DISPLAY} # Option for sharing the display
- QT_X11_NO_MITSHM=1 # For Qt
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw # Share system folder
- /tmp/.docker.xauth:/tmp/.docker.xauth:rw # Share system folder
This section is only relevant for Nvidia graphic cards managed by the Nvidia driver or if you want to have hardware acceleration inside the Docker, e.g. for using CUDA or OpenGL. As pointed out here this is more tricky! Nvidia offers a dedicated nvidia-docker
(with two different options nvidia-docker-1
and nvidia-docker-2
which differ sightly) as well as the nvidia-container-runtime
. Latter was chosen for this guide and will be discussed below.
The installation process of the nvidia-container-runtime
is described here. Before following through with the installation make sure it is not already set-up on your system. For this check the runtime
field from the output of $ docker info
. If nvidia
is available as an option already you should be already good to go.
If it is not available you should be able to install it with the following steps:
$ curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | \
sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | \
sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list
$ sudo apt-get update
$ sudo apt-get install nvidia-container-runtime
and then set-up the Docker runtime:
$ sudo tee /etc/docker/daemon.json <<EOF
{
"runtimes": {
"nvidia": {
"path": "/usr/bin/nvidia-container-runtime",
"runtimeArgs": []
}
}
}
EOF
$ sudo pkill -SIGHUP dockerd
After these steps you might will have to restart the system or at least the Docker daemon with $ sudo systemctl daemon-reload
and $ sudo systemctl restart docker
. Then $ docker info
should output at least two different runtimes, the default runc
as well as nvidia
. This runtime can be set in the Docker-Compose file and one might have to set the environment variables
NVIDIA_VISIBLE_DEVICES=all
and NVIDIA_DRIVER_CAPABILITIES=all
. Be aware that this will make fail launching it on other systems without that runtime. You will need another dedicated Docker-Compose file for it!
A Docker-Compose file now looks as follows:
version: "3.9"
services:
some_name:
build:
context: .
dockerfile: Dockerfile
tty: true
environment:
- DISPLAY=${DISPLAY}
- QT_X11_NO_MITSHM=1
- NVIDIA_VISIBLE_DEVICES=all # Share devices of host system
runtime: nvidia # Specify runtime for container
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw
- /tmp/.docker.xauth:/tmp/.docker.xauth:rw
You can already see that there are quite a few common options between the two. While sadly Docker-Compose does not have conditional execution (yet) one might override or extend an existing configuration file (see also extends
as well this Visual Studio Code guide).
I normally start by creating a base Docker-Compose file docker-compose.yml
version: "3.9"
services:
some_name:
build:
context: .
dockerfile: Dockerfile
tty: true
that is then extended for graphic user interfaces without Nvidia driver docker-compose-gui.yml
,
version: "3.9"
services:
some_name:
extends:
file: docker-compose.yml
service: some_name
environment:
- DISPLAY=${DISPLAY}
- QT_X11_NO_MITSHM=1
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw
- /tmp/.docker.xauth:/tmp/.docker.xauth:rw
on top of that goes another Docker-Compose file with Nvidia acceleration docker-compose-gui-nvidia.yml
version: "3.9"
services:
stereo_matching_docker:
extends:
file: docker-compose-gui.yml
service: stereo_matching_docker
environment:
- NVIDIA_VISIBLE_DEVICES=all
runtime: nvidia
and finally I have yet another file which can be launched for hardware acceleration without graphic user interfaces docker-compose-nvidia.yml
version: "3.9"
services:
stereo_matching_docker:
extends:
file: docker-compose.yml
service: stereo_matching_docker
environment:
- NVIDIA_VISIBLE_DEVICES=all
runtime: nvidia
This way if I need to add additional options, I only have to modify the base Docker-Compose file docker-compose.yml
and the others will simply adapt.
I can then specify which file should be launched to Docker-Compose with e.g.
$ docker-compose -f docker-compose-gui-nvidia.yml up