Introduction

Recently I encountered a situation where I was running code-server inside an Incus container as a remote development environment, while working on an application that included a graphical user interface. Although the development workflow itself worked well, testing and developing the GUI component was not possible directly inside the container. The challenge was enabling GUI applications running in the Incus container to display on the host system, which provides the desktop environment.

So this tutorial demonstrates how to run GUI applications inside an LXC/Incus container and display their user interface on the host system, while still keeping the application fully isolated within the container.

Step 1: Ensure that the host runs X11

Since Wayland is not reliably compatible with all the features of GUI and graphics frameworks such as Qt, JavaFX, or LWJGL, ensure that you run X11 instead of Wayland.

On the host, type

echo $XDG_SESSION_TYPE

This command has to return

x11

If it returns wayland, log out and select Ubuntu on Xorg at the login screen.

Step 2: Setup container-to-X-server connections

To allow local containers to access the X server, you have to adjust the access policy on the host. We’ll use the session profile config to make this permanent.

nano ~/.profile

Append this to the end of your ~/.profile file:

# X11: Allow connections from local containers
xhost +local:

Next, mount the host’s X11 socket into your target container. For that we’ll use a disk device mount within LXC/Incus.

EDITOR=nano incus config edit <YOURCONTAINER>

Here the mount is called mount-x11, but you can call it whatever you want really.

devices:
  mount-x11:
    path: /tmp/.X11-unix
    readonly: "true"
    source: /tmp/.X11-unix
    type: disk

Now restart your container to apply this change.

incus restart <YOURCONTAINER>

This completes all the necessary steps on the host side of things. Next, we move on to configuring the container.

Step 3: Setup the container itself

Change into your container with

incus exec <YOURCONTAINER> bash

Make sure to set required environment variables. Instead of ~/.profile, we’ll use ~/.bashrc now, as ~/.profile triggers at login shells, which our container does not have.

nano ~/.bashrc

Append this to the end of your ~/.bashrc file:

# X11: Set environment variables for host desktop access
export DISPLAY=:1
export XDG_RUNTIME_DIR=/tmp/runtime-$(id -u)

Save your changes and proceed to install these packages:

apt-get install libx11-6 libxext6 libxrender1 libxtst6 libxi6 mesa-utils libgl1

Restart your container and log back into it.

shutdown -r now
incus exec <YOURCONTAINER> bash

Step 4: Testing it out

Now you can test if everything worked out. Here’s the filtered result of GLXInfo for OpenGL for example:

glxinfo | grep OpenGL

OpenGL vendor string: Mesa
OpenGL renderer string: llvmpipe (LLVM 20.1.2, 256 bits)
OpenGL core profile version string: 4.5 (Core Profile) Mesa 25.0.7-0ubuntu0.24.04.2
OpenGL core profile shading language version string: 4.50
OpenGL core profile context flags: (none)
OpenGL core profile profile mask: core profile
OpenGL core profile extensions:
OpenGL version string: 4.5 (Compatibility Profile) Mesa 25.0.7-0ubuntu0.24.04.2
OpenGL shading language version string: 4.50
OpenGL context flags: (none)
OpenGL profile mask: compatibility profile
OpenGL extensions:
OpenGL ES profile version string: OpenGL ES 3.2 Mesa 25.0.7-0ubuntu0.24.04.2
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.20
OpenGL ES profile extensions:

Or run GLXGears to actually see an OpenGL rendered application window.

glxgears
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like
Read More

Add Custom Comment Field to LXC List

This guide will show you how to add a custom comment field to the "lxc list" command, so that meaningful descriptions and additional information can be added to your containers.