Help Ukraine

Docker container with X11 and PulseAudio

Author: Nikita Ermakov a.k.a. sh1r4s3

License: CC BY 4.0

Date: 17 Jan 2022


Sometimes it could be useful to run some multimedia applications inside a container. Personally, I stick with LXC and there are plenty of information about how to pass-through X11, PulseAudio and even Nvidia GPU in the Net. However, there are a few information about how to do that in a Docker container. This small post is merely a reminder for myself but also I hope that it could be useful for someone else. For a ready to use solution one can refer to this GitHub repo.

A bit of theory

PulseAudio. According to the pulseaudio(1), the PulseAudio client libraries check for a set of environment variables, such as $PULSE_SOURCE, $PULSE_SERVER, etc, and these environment variables change the behaviour of the clients. One particular environment variable is interested to us, this is $PULSE_SERVER. The variable contains a string which denotes a list of server addresses, where each entry is separated by a whitespace, which are tried in turn. A server address may contain a prefix with an optional address type specifier, such as unix:, tcp:, etc. For more information please refer to pulseaudio(1). If PulseAudio is running per-user, as in my case, then we can bind-mount the PulseAudio AF_UNIX socket file inside our Docker container. The only issue here is that we need the same UID/GID in the Docker container as in the host system to meet access control rules. After that we can set, in the ~/.bashrc e.g, and use $PULSE_SERVER inside the Docker container, or create a ~/.config/pulse/client.conf configuration file, it is a matter of choice.

X11. Xorg supports different connection types: TCP, UNIX-domain socket, etc. In this post we will stick with UNIX-domain socket as it is the most common case. Basically, we need to bind-mount the socket into the Docker container. Xserver(1) states that the UNIX-domain socket file for the $DISPLAY :0 can be found in /tmp/.X11-unix/X0. X11 has its own display access control. For UNIX-domain socket we can use MIT-MAGIC-COOKIE-1. When a client is using this method it sends a 128-bit "cookie" during connection establishment. The client's copy of the cookie is stored in ~/.Xauthority file. We can use the same cookie, as in the host, in the Docker container. To register the cookie we will use xauth(1) utility.

GPU. For Intel it is quite straightforward to pass-through a GPU into the Docker container. As far as I understand the essential part which should be exposed to the container is Direct Rendering Manager (DRM). To do that we need to simply bind-mount /dev/dri into the container.

WARNING: one should remember about security implications as part of the host system is exposed to the container.

A bit of practice

Let's start with creating of the Docker file. For example I chose my favorite Linux distribution for my desktops, it is Arch:

 1FROM archlinux AS x11-test
 3# Install necessary packages
 4RUN pacman -Sy
 5RUN pacman -S --noconfirm xorg-xauth firefox pulseaudio
 6# Create a group and a user with the same UID/GID as in the host for
 7# PulseAudio to ease its setup. For example UID=1000, GID=1001
 8RUN groupadd -g 1001 foobar
 9RUN useradd -m -u 1000 -g foobar -G audio,video,lp,users foobar
10# Define environment variables for X11 and PulseAudio
12ENV PULSE_SERVER=unix:/tmp/pulse/native
13# Define an argument. This needs to be passed in --build-arg
14ARG cookie
15# Switch to the user and make necessary directories
16USER 1000:1001
17RUN mkdir /tmp/.X11-unix
18RUN mkdir /tmp/pulse
19# Entry script
20RUN : > ~/
21RUN chmod +x ~/
22RUN echo "#!/bin/sh" >> ~/
23RUN echo "xauth add :0 MIT-MAGIC-COOKIE-1 $cookie" >> ~/
24RUN echo "firefox" >> ~/
25# Run entry script
26CMD ~/

The next step is to run the container. We will write a script for that:

2docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix \
3               -v /run/user/1000/pulse/native:/tmp/pulse/native \
4               -v /dev/dri:/dev/dri \
5               x11-test

That's it!