How to easily run graphics-accelerated GUI apps in LXD containers on your Ubuntu desktop

UPDATE June 2020: See newer post at for simplified instructions. They require a recent LXD (version 4.0 or newer), and snap packages work.

Note: This post is about LXD containers. These are system containers, which means they are similar to Docker but behave somewhat like virtual machines. When you start a LXD (lex-dee) container, you are starting a new system on your computer, with its own IP address and all. You can get LXD as a snap package. Go to install snap support, then sudo snap install lxd. See also Getting Started with LXD.

In that older post, we saw how to manually setup a LXD container in order to run GUI apps from there, and have them appear on our X11 desktop.

In this post, we are going to see how to easily set up our LXD installation in order to be able to launch on demand containers that we can run GUI apps in them. First, we will see the instructions and explanation on how to use them. Then, we explain these instructions in detail. And finally we go through some common troubleshooting issues.


The following have been tested with

  • the host runs either Ubuntu 18.04 or Ubuntu 16.04
  • the containers run either Ubuntu 18.04 or Ubuntu 16.04 or Ubuntu 14.04 or Ubuntu 12.04
  • LXD version 3.0 or newer (probably works fine with LXD 2.0.8+ as well)
  • works fine with either the LXD deb package or the LXD snap package

To verify whether you run the deb package or the snap package, run the command which lxd. If the output is /usr/bin/lxd, then you have the deb package of LXD. Otherwise, if it is /snap/bin/lxd, you have the snap package of LXD.

These instructions should work with other distributions as well. Read further below on the detailed explanation of the instructions in order to adapt to your favorite distribution.

In the following, we see the two steps to set up our system so that we can then create GUI containers on demand. Step 1 is only required if you run the deb package of LXD. In subsequent sections, we see an explanation of the instructions so that you can easily port of other Linux distributions. At the end, have a look at the Troubleshooting section to see commons issues and how to solve them.

Step 1 Mapping the user ID of the host to the container

This step is only required if you run the deb package of LXD. If instead you have the snap package of LXD, skip to Step 2.

Run on the host (only once) the following command (source): (Note: if you do not use the bash shell, then $UID is the user-id of the current user. You can replace $UID with $(id -u) in that case.)

$ echo "root:$UID:1" | sudo tee -a /etc/subuid /etc/subgid
[sudo] password for myusername: 

The command appends a new entry in both the /etc/subuid and /etc/subgid subordinate UID/GID files. It allows the LXD service (that runs as root) to remap our user’s ID ($UID, from the host) as requested.

Step 2 Creating the gui LXD profile

You will be creating a LXD profile with settings relevant to launching GUI applications. All configuration that you did manually at the old How to run graphics-accelerated GUI apps in LXD containers on your Ubuntu desktop post, are now included in a single LXD profile.

Download the file lxdguiprofile.txt and save it locally.

Then, create an empty LXD profile with the name gui. Finally, put the downloaded profile configuration into the newly created gui profile.

$ lxc profile create gui
Profile gui created

$ cat lxdguiprofile.txt | lxc profile edit gui

Verify that the profile has been created.

$ lxc profile list
| NAME          | USED BY |
| default       | 10      |
| gui           | 0       |

You can view the contents of the profile gui by running lxc profile show gui. A discussion on the profile contents is found two sections below.

Launching gui containers in LXD

Let’s launch some GUI containers in LXD. The gui LXD profile only has instructions related to running GUI applications. Due to this, you need to specify first another profile with information on the disk and the networking. The default LXD profile is suitable for this. You may use a bridge profile or macvlan profile instead.

$ lxc launch --profile default --profile gui ubuntu:18.04 gui1804
Creating gui1804
Starting gui1804

$ lxc launch --profile default --profile gui ubuntu:16.04 gui1604
Creating gui1604
Starting gui1604

You have launched two containers, with Ubuntu 18.04 and Ubuntu 16.04 respectively. You have specified two LXD profiles, default and gui. This means that the new container gets configuration from default, then from gui.

Next, make sure that the containers are up and running. In the LXD profile there are instructions to install additional packages automatically for us. That takes time. Here is how we check. We get a shell as the non-root account ubuntu in the container, and tail the end of the cloud-init log file. It says that it has 0 failures, it took (on this case) about 22 seconds to complete, and the startup was successful.

$ lxc exec gui1804 -- sudo --user ubuntu --login
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@gui1804:~$ tail -6 /var/log/cloud-init.log 
2018-06-25 13:11:54,175 -[DEBUG]: Ran 20 modules with 0 failures
2018-06-25 13:11:54,176 -[DEBUG]: Creating symbolic link from '/run/cloud-init/result.json' => '../../var/lib/cloud/data/result.json'
2018-06-25 13:11:54,176 -[DEBUG]: Reading from /proc/uptime (quiet=False)
2018-06-25 13:11:54,177 -[DEBUG]: Read 12 bytes from /proc/uptime
2018-06-25 13:11:54,177 -[DEBUG]: cloud-init mode 'modules' took 21.822 seconds (22.00)
2018-06-25 13:11:54,177 -[DEBUG]: finish: modules-final: SUCCESS: running modules for final

Subsequently, run glxgears to test graphics hardware acceleration. You may also try glxinfo.

ubuntu@gui1804:~$ glxgears 
366 frames in 5.0 seconds = 73.161 FPS
300 frames in 5.0 seconds = 59.999 FPS
300 frames in 5.0 seconds = 60.000 FPS

XIO: fatal IO error 11 (Resource temporarily unavailable) on X server ":0"
after 1047 requests (42 known processed) with 0 events remaining.

Finally, test the audio and whether Pulseaudio works.

ubuntu@gui1804:~$ pactl info
Server String: unix:/tmp/.pulse-native
Library Protocol Version: 32
Server Protocol Version: 32
Is Local: yes
Client Index: 12
Tile Size: 65472
User Name: myusername
Host Name: mycomputer
Server Name: pulseaudio
Server Version: 8.0
Default Sample Specification: s16le 2ch 44100Hz
Default Channel Map: front-left,front-right
Default Sink: noechosink
Default Source: noechosource
Cookie: 4a83:ba9b

Audio works fine as well. If there was an error, the pactl info command would have showed it here.

Now, you can install deb packages of GUI programs in these containers, such as Firefox, Chromium browser, Chrome, Steam and so on. Installing snap packages inside the containers and having them appear on your desktop is not supported yet. That would require LXD 3.2 and a few modifications to the profile (not covered in this post).

In the following subsections, we see some useful examples.

Running a separate instance of a program

We are creating a GUI container in order to run Firefox from it. It will be a separate and independent instance of Firefox compared to our desktop browser.

$ lxc launch --profile default --profile gui ubuntu:18.04 firefox
Creating firefox
Starting firefox

$ lxc exec firefox -- sudo --user ubuntu --login
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@firefox:~$ sudo apt install firefox
ubuntu@firefox:~$ firefox

Running old programs in old versions of Ubuntu

redet is a Tcl/Tk program that does not run easily on Ubuntu 18.04 because it needs some extra packaging effort for newer versions of Ubuntu. One option would have been to install Ubuntu 12.04 in VirtualBox. Here is the LXD alternative: We launch an Ubuntu 12.04 container, then install redet and finally run it. It took around 40 seconds from launch to GUI.

$ lxc launch --profile default --profile gui ubuntu:12.04 redet
Creating redet
Starting redet

$ lxc exec redet -- sudo su -l ubuntu
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@redet:~$ sudo apt-get install redet
...ubuntu@redet:~$ redet
Redet 8.26
Copyright (C) 2003-2008 William J. Poser.
This program is free software; you can redistribute it
and/or modify it under the terms of version 3 of the GNU General
Public License as published by the Free Software Foundation.

Running Windows programs with Wine

When you need to run a particular Windows program with Wine, you would prefer not to install all the dependencies on your desktop Ubuntu but rather have them confined into a container. Here is how to do this. We launch a new GUI container called wine, then install Wine (package wine-stable, Wine version 3.0) according to the official instructions, and finally install a Windows program. We can reuse the same container to install more Windows programs.

$ lxc launch --profile default --profile gui ubuntu:18.04 wine
Creating wine
Starting wine

$ lxc exec wine -- sudo --user ubuntu --login
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@wine:~$ sudo dpkg --add-architecture i386 
ubuntu@wine:~$ wget -nc

ubuntu@wine:~$ sudo apt-key add Release.key
ubuntu@wine:~$ sudo apt-add-repository
ubuntu@wine:~$ sudo apt install wine-stable

Then, you can set up the environment to run winetricks in order to easily install Windows programs.

ubuntu@wine:~$ echo export PATH=\"/opt/wine-stable/bin:\$PATH\" >> ~/.profile 
ubuntu@wine:~$ source ~/.profile 
ubuntu@wine:~$ which wine
ubuntu@wine:~$ sudo apt install zenity unzip cabextract
ubuntu@wine:~$ wget
ubuntu@wine:~$ chmod +x winetricks 
ubuntu@wine:~$ ./winetricks

We have installed Internet Explorer through winetricks and here it is,

ubuntu@wine:~$ wine .wine/drive_c/Program\ Files/Internet\ Explorer/iexplore.exe

A closer look into the gui LXD profile

Let’s have a closer look at the gui LXD profile contents.

$ lxc profile show gui
  environment.DISPLAY: :0
  raw.idmap: both 1000 1000
  user.user-data: |
      - 'sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf'
      - 'echo export PULSE_SERVER=unix:/tmp/.pulse-native | tee --append /home/ubuntu/.profile'
      - x11-apps
      - mesa-utils
      - pulseaudio
description: GUI LXD profile
    path: /tmp/.pulse-native
    source: /run/user/1000/pulse/native
    type: disk
    path: /tmp/.X11-unix/X0
    source: /tmp/.X11-unix/X0
    type: disk
    type: gpu
    name: gui
- /1.0/containers/gui1804

The config node

First, there is environment.DISPLAY, with the default value :0. This is an environment variable, and has the value of the default display of the host’s X11 server. You may have to change this to :1 if you have more displays (for example, multiple graphics cards). Here is how to set this to :1,

$ lxc profile set gui environment.DISPLAY :1

The raw.idmap has a value that refers to the sets of $UID/$GID of the non-root user on the host and in the container. By default on Ubuntu the default ID for the first non-root account is 1000 (both user ID and group ID). This is necessary for the bind-mounting of sockets of the host to the container. If you need to change it, here is how to do it. Because we use the both keyword, the first number (i.e. 1000) is the $UID/$GID on the host, and the second number (i.e. 1001) is the $UID/$GID of the non-user account in the container.

$ lxc profile set gui raw.idmap "both 1000 1001"

The user.user-data are instructions for cloud-init. The LXD container images from the ubuntu: repository support cloud-init, and we use it to pass configuration to the newly created container.

In cloud-init, we use runcmd to run two commands. First, disable shm in PulseAudio so that it uses an alternative that works in LXD. Second, set the PULSE_SERVER environment variable to the Unix socket that have bind-mounted in the devices node.

In packages, we get cloud-init to install for us the minimal packages to get X11 libraries, Mesa libraries and the PulseAudio client libraries. On top of that, we get cloud-init to run apt update for us so that when we get into the container, we can install packages straight away.

The description node

This node has the description text of the LXD profile.

The devices node

The devices node has two Unix sockets, one for PulseAudio and one for X11.

It also gives access to the gpu device.

The used_by node

We do not edit this node, it will include the created containers that have this profile.

Creating shortcuts to the gui container applications

If you want to run Internet Exploerr from the container, you can simply run from a terminal window the following,

$ lxc exec wine -- sudo --login --user ubuntu /opt/wine-stable/bin/wine /home/ubuntu/.wine/drive_c/Program\ Files/Internet\ Explorer/iexplore.exe

and that’s it.

To make a shortcut, create the following .desktop file on the host and finally use desktop-file-install to install into /usr/share/applications/.

$ cat > lxd-iexplore.desktop
[Desktop Entry]
Name=Internet Explorer in LXD
Comment=Access the Internet with Wine Internet Explorer through a LXD container
Exec=lxc exec wine -- sudo --login --user ubuntu /opt/wine-stable/bin/wine /home/ubuntu/.wine/drive_c/Program\ Files/Internet\ Explorer/iexplore.exe %U
$ sudo desktop-file-install lxd-iexplore.desktop

This is how the (randomly-selected) icon looks like in a File Manager.

Here is the icon on the Launcher. Simply drag from the File Manager and drop to the Launcher in order to get the application at your fingertips.


Error sudo: unknown user: ubuntu and unable to initialize policy plugin

You get this error when you create a container and then very quickly try to connect to it with a shell. Here is how it looks.

$ lxc launch --profile default --profile gui ubuntu:18.04 gui1804
Creating gui1804
Starting gui1804

$ lxc exec gui1804 -- sudo --user ubuntu --login
sudo: unknown user: ubuntu
sudo: unable to initialize policy plugin

The Ubuntu container images come with cloud-init instructions that, among others, create the non-root account ubuntu. When you launch a container, it takes several seconds to start the runtime and then execute the cloud-init instructions. You get this error when you try to connect too soon, when the ubuntu account has not been created yet. You can try again until the account is created.

Error Pulseaudio, Connection failure: Connection refused

You got a shell in the newly created container, but when you try to use the audio, you get Connection refused. Here is how it looks,

ubuntu@gui1804:~$ pactl info
Connection failure: Connection refused

The cloud-init instructions in the gui LXD profile have commands to install packages and commands to setup the PulseAudio environment variable. The sequence is to install first the packages, and then add PULSE_SERVER in the ~/.profile. This means that if you get a shell in the container before cloud-init has completed, you have missed the addition of PULSE_SERVER in ~/.profile. As a solution, you can log out and then connect again. Or, do

ubuntu@gui1804:~$ source ~/.profile
ubuntu@gui1804:~$ pactl info
Server String: unix:/tmp/.pulse-native
Library Protocol Version: 32
Server Protocol Version: 32

I have an existing container, can I make it a gui container?

Yes, you can. You can assign profiles to a container, then restart the container. Here is how,

$ lxc profile assign oldcontainer default,gui
Profiles default,gui applied to oldcontainer
$ lxc restart oldcontainer

I have a gui container, can I remove the gui profile from it?

Yes, by assigning the default or any other profile. Then, restart the container.

$ lxc profile assign gui1804 default
Profiles default applied to gui1804
$ lxc restart gui1804

More errors

Report in the comments any issues that you encounter and I will be adding here.

I tested this on both Intel and AMD GPUs and they worked fine for me. For NVidia there might be some additional issues, so I would rather investigate again rather than copy from the old post.


A year ago, I wrote the first version of the post on how to run GUI application in a LXD system container. I had put together older sources from the Internet while writing that post. In this post, I used the comments and feedback of last year’s post to automate the process and make it less error-prone.

Up to now, we have seen how to reuse the existing display of our desktop for any GUI apps running in a container. The downside is that a malicious application in a container can attack the desktop because X11. One solution is to use Xephyr instead of our desktop’s DISPLAY (:0). It is elemental to adapt this post to use Xephyr. However, in terms of usability, it would be ideal to create some sort of VirtualBox clone that would use LXD containers instead of VMs to launch Linux distributions. In this VirtualBox clone, it would be easy to select whether we want to output in a window on the desktop’s DISPLAY or in a Xephyr window. Also, in a Xephyr window we can launch a window manager, therefore we can have a proper Linux desktop environment in a window.

Permanent link to this article:

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: