How to run GUI apps in LXD containers
Tags: tutorial, linux, tooling, devjournal
Published: 2022-08-04 13:42:43

I recently tried to run GUI apps in LXD containers and had a hard time doing it. Here is the solution I found and the problems I encountered.

We will assume a basic knowledge of LXD. It is possible to follow this article with a very limited knowledge of it. But we will not start from scratch by explaining what it is and how to use it.

I will explain how I did it on a Ubuntu host, by using the host Xorg socket in containers that use the `ubuntu:22.04` image.
<br><br>
## Why use GUI apps in a LXD container

Before doing anything, we should probably discuss why we may want to do that. Here is why I did it:

1. Running apps in a container is more secure. They still have access to other windows because we use the host Xorg socket. But they run in a separated filesystem and have only access to what we give them.
2. It is easier to make backup by using the snapshots and backups features provided by LXD.
3. We can use them for temporary tests without polluting the host machine. And then delete them as if they had never been there.

My main goal was testing applications I don't fully trust while staying the most secure I can. And I then found that it is really nice to have the ability to make snapshots and clone containers, especially for dev environments.
<br><br>
## A way to run GUI apps in a LXD container

Here begins the journey!
<br>
### 1. Creating a profile and forwarding the Xorg socket

First we need to forward the host Xorg socket. It will allow the container to display GUI without having to install its own window system.

To make it easier, we can create a profile. We will then be able to start multiple containers without having to write everything again.

To create a profile, run:
```
lxc profile create <profile name>
```

And edit it to add the following config:
```
config:
  environment.DISPLAY: :0
description: ""
devices:
  X0:
    bind: container
    connect: unix:/tmp/.X11-unix/X1
    listen: unix:/mnt/X0
    security.gid: "1000"
    security.uid: "1000"
    type: proxy
name: <profile name>
```

What it does is:
- set the `DISPLAY` environment variable with the value `:0` (used to know which socket to use)
- proxy the host Xorg socket located at `/tmp/.X11-unix/X1` to the container at `/mnt/X0` (you might need to replace the `X1` by something else)

_Warning_: We do not want to proxy the socket to `/tmp/.X11-unix/X0` in the container. Otherwise it will be deleted on reboot because it is located on the `/tmp` folder. See below how to do it.
<br>
### 2. Starting a container

Now that we have our profile, we are able to create new containers with it.

To start a new container, run:
```
lxc launch --profile default --profile <profile name> <image> <container name>
```

Here we are using the official `ubuntu:22.04` image, so run:
```
lxc launch --profile default --profile <profile name> ubuntu:22.04 <container name>
```

And we can then enter the container by running:
```
lxc exec <container name> -- bash
```

And here is our dear terminal where we can do almost anything!
<br>
### 3. Creating a symlink for the Xorg socket

We created a proxy for the host Xorg socket. But it is located at `/mnt/X0`. Not in `/tmp/.X11-unix/X0` were it needs to be.

We did that to avoid the socket being deleted when the system starts. Because the `/tmp` would be emptied on boot.

To make it work, we are going to create a symlink from `/mnt/X0` to `/tmp/.X11-unix/X0` by running the following command:
```
ln -s /mnt/X0 /tmp/.X11-unix/X0
```

Now we can use GUI apps inside our container. But it is not really user friendly.
<br>
### 4. Make the symlink automatic on startup

Until now, we are able to use GUI apps in out container. But we have to create the symlink manually everytime we start the container. We will make it automatic to avoid this pain.

To do that we can use multiple solutions. Here, we will use CRON jobs.

Run:
```
crontab -e
```

And add this in the editor:
```
@reboot ln -s /mnt/X0 /tmp/.X11-unix/X0
```

If we want to be able to run GUI apps from other users, we can also change the socket permissions by adding this CRON job:
```
@reboot chmod a+wx /tmp/.X11-unix/X0
```

And now we can restart our container as much as we cant without having anything manual to do anymore!
<br>
### 5. Install GUI apps and run them

This step is just the fun part!

Just install any GUI app in your container and start it. Everything should work as a charm. (If you are as lucky as me.)
<br><br>
## Conclusion

Running GUI apps in LXD containers is not that simple. And some guides that we can find on the Internet even make it harder by guiding us in the wrong direction.

The main challenges I found were not documented enough were:
1. The need to proxy the Xorg socket to anywhere else than the `/tmp` folder. Otherwise it would work. But only before we restart the container.
2. Setting the permissions on the socket to allow other users to run GUI apps. Because even in containers we may not want to use them as root. And Electron apps do not like to be run as root.

I hope this article helped. (And "Hi!" to my future self 😁️)

Feedback and tips are more than welcome, don't hesitate to reach out!