How to run dockerized X Windows apps on macOS
- Install XQuartz
brew install --cask xquartz, reboot
xhost +localhostto enable XClients to connect to XServer over local TCP/IP connections
docker run -it --rm --env="DISPLAY=host.docker.internal:0" ...
There are numerous examples how to run X-Window applications in a docker container, but very few of the examples work on macOS using "Docker for Mac" product. I discovered that trying to work through the tutorials of ROS2 (Robot Operating System) on my Mac (Big Sur, M1 CPU). While most of ROS processes work perfectly well in docker, some essential GUI applications such as rqt and rviz need to connect to XServer process running on the host machine. What follows is a list of steps to make this setup work using "Docker for Mac" and XQuartz.
The first step is to install XQuartz application, which implements X11 system on macOS. You can either download dmg from here or install XQuartz using Homebrew (my preferred method). Please note that Homebrew has changed the command to install "cask" packaged apps, and the correct command to install is available at https://formulae.brew.sh/cask/xquartz#default
brew update # optional brew install --cask xquartz
After the installation completes, start XQuartz application, select Preferences menu, go to the “Security” tab and make sure you’ve got “Allow connections from network clients” checked:
After that quit and restart XQuartz application. In fact, I had to restart my machine after which things started magically working, so it may or may not be a factor. A quick test of XQuartz is to run
xeyesfrom the command line, and you should see a pair of googly eyes watching the position of the mouse.
Permissions control with xhost command
There are several mechanisms to manage access control of an X server - xhost and xauth.
xhostis a simple mechanism to have an IP based access control, and it's good enough for non-production use. Note that the access control settings set by
xhostwill only persist while XQuartz application is running. Restarting XQuartz application resets the access control to the most restrictive mode (equivalent to
xhost -with no authorized clients), and it's a good idea to quit XQuartz in the end.
Quick reference for working with xhost:
xhostprints the current mode and the list of authorized clients
xhost +disables access control and allows any client from the network to connect to XServer.
xhost -enables access control and allows only authorized clients to connect
xhost +localhostadds localhost to the list of authorized clients, allowing only local processes to connect
For our purpose it sufficient to run
xhost +localhostto allow docker process to connect to XQuartz XServer.
Theoretically, all we need to do is to share
/tmp/.X11-unixdirectory to allow X11 apps inside a container to communicate with the XServer on the host using Unix Domain Sockets (UDS). This approach is described in multiple articles available on this topic, just search for "how to run GUI/X11 apps in docker".
Unfortunately this approach will not work when using "Docker for Mac". There is a long-standing issue for Docker for Mac that makes Unix style sockets non-functional between the host and a container. That issue is related to docker's
osxfsfilesystem driver implementation, and was closed with 'will not fix' resolution. The Docker Desktop 184.108.40.206 release switched from
osxfsto gRPC-FUSE file sharing driver. However, after a quick test, I confirmed the issue has not been fixed in the gRPC-FUSE implementation either.
The workaround for this issue is to use TCP sockets for the XSystem communication instead of Unix Domain Sockets. It is not as performant as connecting over UDS, but XSystem was designed to work over the network. The only trick to make this work is to set
DISPLAYenvironment variable inside the container to point at the host display, which is addressable as
With the following Dockerfile
FROM ubuntu:focal RUN apt-get update \ && apt-get install -y --no-install-recommends \ x11-apps CMD ["xeyes"]
docker build -t x11-apps .and run it
docker run -it --rm --env="DISPLAY=host.docker.internal:0" x11-apps xeyes
If you got the following output, you probably need to start/restart XQuartz, start it now with
No protocol specified Error: Can't open display: host.docker.internal:0
Right now this Dockerfile installs only x11-apps, but you would need to install the actual packages you want to use to the image.
One outstanding issue here is GPU support: for the apps requesting GPU context (e.g. rviz for ROS) I have not gotten this to work. If you have an idea how to approach that, let me know in the comments.