Desktop applications in containers

I have been playing heavily with docker in the last couple of weeks and the idea of encapsulating applications including all of their dependencies and cruft they bring into a kind of ‘sub-system’ that only has well defined shared resources with the host did not only speak to me when thinking about servers and development environments. I have seen a trend with modern, closed source applications: They all start to provide their own repository for your package manager instead of bothering with the official ones. Adding a third party repository to your package manager simply to install spotify or slack is a question of trust - the list of third party repositories should be minimal.

Dockerize it

Since in Linux everything is a file and docker can mount files to containers the thought of putting applications into containers is not very far fetched: It’s as easy as mounting the correct set of sockets to the container and the containerized application is able to talk to the system resources.

X11

In order for graphical output to work there are 3 things that need to be done:

  1. The host must allow remote connections to X11 (since the container is seen as remote from the point of X11). This can be done by using xhost local:root
  2. The X11 socket (Located under /tmp/.X11-unix) needs to be mounted to the container
  3. The $DISPLAY environment variable needs to be passed down to the container

To test if the connection to X11 is working correctly the following can be executed to setup a simple container containing the xeyes application:

#!/bin/bash

docker build -t 'thej6s/xeyes' -  << __EOF__
FROM debian
RUN apt-get update && apt-get install -y x11-apps
ENV DISPLAY $DISPLAY
CMD xeyes
__EOF__

XSOCK=/tmp/.X11-unix
xhost local:root
docker run -v $XSOCK --net host 'thej6s/xeyes'

xeyes running inside of docker

Sound: Alsa

The next big hardware device that a desktop application might want to use is sound input and output. The simplest way is to let the guest handle all of the audio related tasks using alsa acessing the audio device directly. This would work similar to the X11 socket above - but with the /dev/snd device.

This works - but has a major drawback: It places all of the control over audio into the containers. Imagine having to ssh into multiple containers to regulate your volume.

Sound: Pulseaudio

Most distributions and most users are using pulseaudio in order to configure and manager their sound environment. A dockerized application should play into the global pulse instance instead of acessing the audio device directly. This way all dockerized applications are still managable by using a tool such as pavucontrol on the host.

This however presents a couple of difficulties: - Pulseaudio is started as a user service and is bound to the current machine and user

In order to overcome these hurdles a couple of steps need to be taken: 1. Create an environment that is accepted by pulseaudio IPC - Create a user in the container with the same uid as the user on the host system - Mount /etc/machine-id into the container 2. Mount the pulse audio socket (/run/user/${UID}/pulse) into the container

The following starts firefox in a container with support for pulseaudio for sound:

XSOCK=/tmp/.X11-unix
UID=$(id -u)

docker build -t 'j6s/firefox' - << __EOF__
FROM debian

RUN apt-get update && apt-get install -y firefox-esr

ENV HOME /home/user
RUN useradd -u ${UID} \
        --create-home --home-dir \
        /home/user user && \
    usermod -a -G audio user && \
    chown -R user:user /home/user

USER user
WORKDIR /home/user
CMD firefox-esr
__EOF__

docker run --rm \
    -v $XSOCK:$XSOCK \
    -v /etc/machine-id:/etc/machine-id \
    -v /run/user/${UID}/pulse:/run/user/${UID}/pulse \
    -e "DISPLAY=${DISPLAY}" \
    --name firefox \
    'j6s/firefox' \

Firefox running inside of a container

Spotify

Let’s revisit how I started this article: The idea of encapsulating third party closed source applications appealed to me - that was the point of all of this. Spotify is the easiest example, as all that it needs is X11 and sound output.

#!/bin/bash

XSOCK=/tmp/.X11-unix
UID=$(id -u)
DIR=$(pwd)

function run {
	echo -e "$ $@"
	eval $@
}

run mkdir -p data/config data/cache
run chown -R ${UID} data/
run chmod -R 755 data/

run docker build -t 'j6s/spotify' - << __EOF__
FROM debian

RUN apt-get update && apt-get install -y gpg
RUN apt-key adv \
        --keyserver hkp://keyserver.ubuntu.com:80 \
        --recv-keys 931FF8E79F0876134EDDBDCCA87FF9DF48BF1C90 && \
    echo 'deb http://repository.spotify.com stable non-free' > /etc/apt/sources.list.d/spotify.list && \
    apt-get update &&\
    apt-get install -y -q --no-install-recommends spotify-client

RUN apt-get install -y -q --no-install-recommends \
        pulseaudio \
        libgl1-mesa-dri \
        libgl1-mesa-glx

ENV HOME /home/user
RUN useradd -u ${UID} --create-home --home-dir /home/user user && \
    usermod -a -G audio user && \
    chown -R user:user /home/user

USER user
WORKDIR /home/user
CMD spotify
__EOF__

run docker run --rm \
	-v $XSOCK:$XSOCK \
	-v /etc/machine-id:/etc/machine-id \
	-v /run/user/${UID}/pulse:/run/user/${UID}/pulse \
	-v ${DIR}/data/config:/home/user/.config \
	-v ${DIR}/data/cache:/home/user/.cache \
	-e "DISPLAY=${DISPLAY}" \
	--name spotify \
	'j6s/spotify' \

Spotify running inside of a docker container

Further reading

The following blogpost (and especially the github repository by the author) is very interesting when it comes to desktop applications running inside of containers: https://blog.jessfraz.com/post/docker-containers-on-the-desktop/

I wrote a follow-up article: Encapsulating nonfree applications using docker