Running Dockerized GUI Applications

I had seen the blog on this by Jessie Frazelle before. Now I had to edit some files with a very non-free wire-frame mock GUI tool that we use for a project at the office. There was a binary package for Ubuntu/Debian. I figured I'd stuff it in a Docker container to be on the safe side and then just

docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix \
    -e DISPLAY=unix$DISPLAY image command

to give it a spin first and rerun with the files volume-mounted at a suitable location to get things over with.

Alas, no such luck. All I got was a

No protocol specified
Error: Can't open display: unix:0.0

A fair bit of reading later I finally came across a well-informed solution on Stack Overflow (SO) that solved things for me, after a bit of tinkering.

The Sledgehammer Approach

Plain and simple, turn off access control to the X server with

xhost +

Anyone who can access your X server can now display whatever they like on your display. That includes that docker container (as long as you volume-mount the /tmp/.X11-unix socket). If you're the only user on your system and your X is run with the -nolisten tcp option, that may be acceptable.

Just turning off the access control makes the above docker command work as expected. Great, why bother beyond this? Well, for one thing, you may not be the only user on the system. Maybe your X server does listen for TCP connections.

In that case, turn the access controls back on with

xhost -

Fine-Grained X Server Access Control

It's very likely that there is an $XAUTHORITY environment variable set in your current desktop session. In case there isn't, check if there's an .Xauthority file in your $HOME directory. Yes? Good, then we can use that to allow the docker container access to the X server.

docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix \
    -h $(hostname) \
    -e DISPLAY=$DISPLAY image command

Works like a charm, doesn't it? Thing is, you don't really want to run two containers with the same host name. The docker engine will let you (and the container names differ), but for a mere human like myself it is just too confusing. Problem is, the X server may just be checking against the hostname that's embedded in that $XAUTHORITY file. Now what?

The solution on SO basically gives the docker container access to a "wildcard" copy of your X11 session's authorisation cookie to access the X server. That means that the X server no longer gives a hoot about the host name. However, the SO solution creates that copy in a potentially insecure manner. This comes about because the -f option to xauth requires the target file to exist. Let's do this a bit more carefully and robustly

echo "ffff 0000  $(xauth nlist $DISPLAY | cut -d\  -f4-)" \
    | xauth -f $DOCKER_XAUTH nmerge -

The above puts the file next to your regular authorisation file and takes care to preserve permissions. It also removes the host's name so that the file does not leak this information to the container.

Now you should have no problem running your dockerized GUI application with a command-line like

docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix \
    -e DISPLAY=$DISPLAY image command

While the above tries to secure things as much as possible, the GUI application that runs in the container can still trash the host's X server and exploit any holes in it. Caveat emptor.

Further Tinkering

Please note that the $SESSIONXAUTH file is recreated with a different cookie every time you start your X session. Therefore, you need to update the $DOCKER_XAUTH file whenever that file changes. Also, that docker invocation is getting rather long. Looks like it's time for a little script.

That's it for today.

See also: XSecurity(7) manual page for more information on the various access methods and their security implications.