How to run a GUI (with sound) in a Docker Container (MacOS & Linux)
Tags: docker, python, gui
Published: 2022-07-04 09:20:45

I have recently been working on a webscraping project, that gathers large amount of data from an online bookstore. My final version implements different functionalities, that allows the user to choose the book categories they are interested in (fiction, drama, etc) & input their postcode to receive information about the closest bookshop where they can buy different books. To make the interaction with my webscraper easier and more fun, I have implemented a Python GUI (Tkinter module).

The previous version of my project was already containerised using Docker, so naturally I started thinking how I could now containerise my GUI application.  While Docker is normally used to containerise CLI programs and background applications, you can also use it to run graphical user interfaces (GUI).

**Using an existing X server for visualisation**

An X server is an application that manages one or more graphic displays. GUI applications can’t be displayed without an available X server. Services can communicate with the X server to display graphical interfaces and receive input from the user.

**1.MacOS**

XQuartz is an open-source version of X server, that allows cross-platform applications using X11 for GUIs to run on macOS (even if they are not specifically designed for macOS).  This software can be installed locally using brew (**brew install xquartz**).

After successful installation, XQuartz can be open locally. Firstly, we will need to configure its security preferences. This can be done by accessing **Preferences --> Security & allowing connections from network clients**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfzxz7codyy6xf6g9wsh.png)
 
The DISPLAY environment variable will instruct the X client which X server to connect to. The X display server install itself normally as the display number 0 on your local machine. However, the ‘local machine’ refers to the machine on which we run the application, in our case the docker container. We need to re-route this display to our ‘true’ local machine. 

The server knows where to redirect the X network traffic via the DISPLAY environment variable. To set this up, we need to follow these steps:

- Get the Host IP: **IP=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}')**

- Set the DISPLAY environment variable: **export DISPLAY=$IP:0**

- Add Path to our xhost: **export PATH=/usr/X11/bin/xhost:$PATH**

- Add IP to xhost: **xhost + $IP**


**2.Linux**

On Linux, we will make use of **sshd**, an OpenSSH server component, that listens continuously for client connections. When a connection request occurs, sshd sets up the correct connection depending on the type of client tool connection. We can manually configure the OpenSSH server by accessing the **sshd_config file**. This can normally be found in **/etc/sshd/sshd_config**. 

Once inside this file, **set X11Forwarding, X11UseForwarding and X11UseLocalhost to yes**. Lastly, set the display environment variable **DISPLAY:=0**. This allows to forward the traffic from the host (the docker container) to your local computer. 

**Setting up the audio interface**

To further complicate things, I decided to add a background audio to the GUI. More specifically, when running the GUI, the Harry Potter theme song will start playing on the background. This seemed fitting for a book webscraper application. 

This means that we need to also set up the sound for the docker container. This can be done using **Pulseaudio**, a sound server system that allow transferring audio to different machines. Pulseaudio can be provided with a shared unix socket or with a TCP connection, but both ways need a Pulseuadio server on the host and in the docker image. 

To install Pulseaudio & all its dependencies, you can add the following line to your Dockerfile:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ayjht3azoroah3mmg11.png)

In this case I used a TCP connection to set up Pulseaudio. To do this, we need to enable the TCP module on the server, by navigating to **usr/local/Cellar/pulaseaudio/<version>/etc.pulse/default.pa** (MacOS) or **etc/pulse/default.pa** (Linux), and uncomment the following two lines:

- **load-module module-esound-protocol-tcp**

- **load-module module-native-protocol-tcp**

**Running the docker container**

To finally run the docker container we will use the following syntax: 
**docker run -it --rm -v ~/.config/pulse:/root/.config/pulse -e DISPLAY=IP:0 -e PULSE_SERVER=IP:4713 <name_of_container>**

 

- v ~/.config/pulse:/root/.config/pulse - makes sure both client and server have same pulseaudio cookie file 

- -e DISPLAY=IP:0 -sets the visual display

- e PULSE_SERVER=IP:4713 -default pulseaudio port is 4713 but you can double check by running lsof -PiTCP -sTCP:LISTEN