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

In How to run Wine (graphics-accelerated) in an LXD container on Ubuntu we had a quick look into how to run GUI programs in an LXD (Lex-Dee) container, and have the output appear on the local X11 server (your Ubuntu desktop).

In this post, we are going to see how to

  1. generalize the instructions in order to run most GUI apps in a LXD container but appear on your desktop
  2. have accelerated graphics support and audio
  3. test with Firefox, Chromium and Chrome
  4. create shortcuts to easily launch those apps

The benefits in running GUI apps in a LXD container are

  • clear separation of the installation data and settings, from what we have on our desktop
  • ability to create a snapshot of this container, save, rollback, delete, recreate; all these in a few seconds or less
  • does not mess up your installed package list (for example, all those i386 packages for Wine, Google Earth)
  • ability to create an image of such a perfect container, publish, and have others launch in a few clicks

What we are doing today is similar to having a Virtualbox/VMWare VM and running a Linux distribution in it. Let’s compare,

  • It is similar to the Virtualbox Seamless Mode or the VMWare Unity mode
  • A VM virtualizes a whole machine and has to do a lot of work in order to provide somewhat good graphics acceleration
  • With a container, we directly reuse the graphics card and get graphics acceleration
  • The specific set up we show today, can potential allow a container app to interact with the desktop apps (TODO: show desktop isolation in future post)

Browsers have started having containers and specifically in-browser containers. It shows a trend towards containers in general, it is browser-specific and is dictated by usability (passwords, form and search data are shared between the containers).

In the following, our desktop computer will called the host, and the LXD container as the container.

Setting up LXD

LXD is supported in Ubuntu and derivatives, as well as other distributions. When you initially set up LXD, you select where to store the containers. See LXD 2.0: Installing and configuring LXD [2/12] about your options. Ideally, if you select to pre-allocate disk space or use a partition, select at least 15GB but preferably more.

If you plan to play games, increase the space by the size of that game. For best results, select ZFS as the storage backend, and place the space on an SSD disk. Also Trying out LXD containers on our Ubuntu may help.

Creating the LXD container

Let’s create the new container for LXD. We are going to call it guiapps, and install Ubuntu 16.04 in it. There are options for other Ubuntu versions, and even other distributions.

$ lxc launch ubuntu:x guiapps
Creating guiapps
Starting guiapps
$ lxc list
+---------------+---------+--------------------+--------+------------+-----------+
|     NAME      |  STATE  |        IPV4        |  IPV6  |    TYPE    | SNAPSHOTS |
+---------------+---------+--------------------+--------+------------+-----------+
| guiapps       | RUNNING | 10.0.185.204(eth0) |        | PERSISTENT | 0         |
+---------------+---------+--------------------+--------+------------+-----------+
$

We created and started an Ubuntu 16.04 (ubuntu:x) container, called guiapps.

Let’s also install our initial testing applications. The first one is xclock, the simplest X11 GUI app. The second is glxinfo, that shows details about graphics acceleration. The third, glxgears, a minimal graphics-accelerated application. The fourth is speaker-test, to test for audio. We will know that our set up works, if all three xclock, glxinfo, glxgears and speaker-test work in the container!

$ lxc exec guiapps -- sudo --login --user ubuntu
ubuntu@guiapps:~$ sudo apt update
ubuntu@guiapps:~$ sudo apt install x11-apps
ubuntu@guiapps:~$ sudo apt install mesa-utils
ubuntu@guiapps:~$ sudo apt install alsa-utils
ubuntu@guiapps:~$ exit $

We execute a login shell in the guiapps container as user ubuntu, the default non-root user account in all Ubuntu LXD images. Other distribution images probably have another default non-root user account.

Then, we run apt update in order to update the package list and be able to install the subsequent three packages that provide xclock, glxinfo and glxgears, and speaker-test (or aplay). Finally, we exit the container.

Mapping the user ID of the host to the container (PREREQUISITE)

In the following steps we will be sharing files from the host (our desktop) to the container. There is the issue of what user ID will appear in the container for those shared files.

First, we run on the host (only once) the following command (source),

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

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

Then, we specify that we want this feature in our guiapps LXD container, and restart the container for the change to take effect.

$ lxc config set guiapps raw.idmap "both $UID 1000"
$ lxc restart guiapps
$

This “both $UID 1000” syntax is a shortcut that means to map the $UID/$GID of our username in the host, to the default non-root username in the container (which should be 1000 for Ubuntu images, at least).

Configuring graphics and graphics acceleration

For graphics acceleration, we are going to use the host graphics card and graphics acceleration. By default, the applications that run in a container do not have access to the host system and cannot start GUI apps.

We need two things; let the container to access the GPU devices of the host, and make sure that there are no restrictions because of different user-ids.

Let’s attempt to run xclock in the container.

$ lxc exec guiapps -- sudo --login --user ubuntu
ubuntu@guiapps:~$ xclock
Error: Can't open display: 
ubuntu@guiapps:~$ export DISPLAY=:0
ubuntu@guiapps:~$ xclock
Error: Can't open display: :0
ubuntu@guiapps:~$ exit
$

We run xclock in the container, and as expected it does not run because we did not indicate where to send the display. We set the DISPLAY environment variable to the default :0 (send to either a Unix socket or port 6000), which do not work either because we did not fully set them up yet. Let’s do that.

$ lxc config device add guiapps X0 disk path=/tmp/.X11-unix/X0 source=/tmp/.X11-unix/X0 
$ lxc config device add guiapps Xauthority disk path=/home/ubuntu/.Xauthority source=${XAUTHORITY}

We give access to the Unix socket of the X server (/tmp/.X11-unix/X0) to the container, and make it available at the same exactly path inside the container. In this way, DISPLAY=:0 would allow the apps in the containers to access our host’s X server through the Unix socket.

Then, we repeat this task with the ~/.Xauthority file that resides in our home directory. This file is for access control, and simply makes our host X server to allow the access from applications inside that container. For the host, this file can be found in the variable $XAUTHORITY and should be either at ~/.Xauthority or /run/myusername/1000/gdm/Xauthority. Obviously, we can set correctly the source= part, however the distribution in the container needs to be able to find the .Xauthority in the given location. If the container is the official Ubuntu, then it should be /home/ubuntu/.Xauthority Adjust accordingly if you use a different distribution. If something goes wrong in the whole guide, it most probably will be in this above two commands.

How do we get hardware acceleration for the GPU to the container apps? There is a special device for that, and it’s gpu. The hardware acceleration for the graphics card is collectively enabled by running the following,

$ lxc config device add guiapps mygpu gpu
$ lxc config device set guiapps mygpu uid 1000
$ lxc config device set guiapps mygpu gid 1000

We add the gpu device, and we happen to name it mygpu (any name would suffice). In addition to gpu device, we also set the permissions accordingly so that the device is fully accessible in  the container. The gpu device has been introduced in LXD 2.7, therefore if it is not found, you may have to upgrade your LXD according to https://launchpad.net/~ubuntu-lxc/+archive/ubuntu/lxd-stable Please leave a comment below if this was your case (mention what LXD version you have been running). Note that for Intel GPUs (my case), you may not need to add this device.

Let’s see what we got now.

$ lxc exec guiapps -- sudo --login --user ubuntu
ubuntu@guiapps:~$ export DISPLAY=:0
ubuntu@guiapps:~$ xclock

ubuntu@guiapps:~$ glxinfo -B
name of display: :0
display: :0  screen: 0
direct rendering: Yes
Extended renderer info (GLX_MESA_query_renderer):
    Vendor: Intel Open Source Technology Center (0x8086)
...
ubuntu@guiapps:~$ glxgears 

Running synchronized to the vertical refresh.  The framerate should be
approximately the same as the monitor refresh rate.
345 frames in 5.0 seconds = 68.783 FPS
309 frames in 5.0 seconds = 61.699 FPS
300 frames in 5.0 seconds = 60.000 FPS
^C
ubuntu@guiapps:~$ echo "export DISPLAY=:0" >> ~/.profile 
ubuntu@guiapps:~$ exit
$

Looks good, we are good to go! Note that we edited the ~/.profile file in order to set the $DISPLAY variable automatically whenever we connect to the container.

Configuring audio

The audio server in Ubuntu desktop is Pulseaudio, and Pulseaudio has a feature to allow authenticated access over the network. Just like the X11 server and what we did earlier. Let’s do this.

We install the paprefs (PulseAudio Preferences) package on the host.

$ sudo apt install paprefs
...
$ paprefs

This is the only option we need to enable (by default all other options are not check and can remain unchecked).

That is, under the Network Server tab, we tick Enable network access to local sound devices.

Then, just like with the X11 configuration, we need to deal with two things; the access to the Pulseaudio server of the host (either through a Unix socket or an IP address), and some way to get authorization to access the Pulseaudio server. Regarding the Unix socket of the Pulseaudio server, it is a bit of hit and miss (could not figure out how to use reliably), so we are going to use the IP address of the host (lxdbr0 interface).

First, the IP address of the host (that has Pulseaudio) is the IP of the lxdbr0 interface, or the default gateway (ip link show). Second, the authorization is provided through the cookie in the host at /home/${USER}/.config/pulse/cookie Let’s connect these to files inside the container.

$ lxc exec guiapps -- sudo --login --user ubuntu
ubuntu@guiapps:~$ echo export PULSE_SERVER="tcp:`ip route show 0/0 | awk '{print $3}'`" >> ~/.profile

This command will automatically set the variable PULSE_SERVER to a value like tcp:10.0.185.1, which is the IP address of the host, for the lxdbr0 interface. The next time we log in to the container, PULSE_SERVER will be configured properly.

ubuntu@guiapps:~$ mkdir -p ~/.config/pulse/
ubuntu@guiapps:~$ echo export PULSE_COOKIE=/home/ubuntu/.config/pulse/cookie >> ~/.profile
ubuntu@guiapps:~$ exit
$ lxc config device add guiapps PACookie disk path=/home/ubuntu/.config/pulse/cookie source=/home/${USER}/.config/pulse/cookie

Now, this is a tough cookie. By default, the Pulseaudio cookie is found at ~/.config/pulse/cookie. The directory tree ~/.config/pulse/ does not exist, and if we do not create it ourselves, then lxd config will autocreate it with the wrong ownership. So, we create it (mkdir -p), then add the correct PULSE_COOKIE line in the configuration file ~/.profile. Finally, we exit from the container and mount-bind the cookie from the host to the container. When we log in to the container again, the cookie variable will be correctly set!

Let’s test the audio!

$ lxc exec guiapps -- sudo --login --user ubuntu
ubuntu@pulseaudio:~$ speaker-test -c6 -twav

speaker-test 1.1.0

Playback device is default
Stream parameters are 48000Hz, S16_LE, 6 channels
WAV file(s)
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 32 to 349525
Period size range from 10 to 116509
Using max buffer size 349524
Periods = 4
was set period_size = 87381
was set buffer_size = 349524
 0 - Front Left
 4 - Center
 1 - Front Right
 3 - Rear Right
 2 - Rear Left
 5 - LFE
Time per period = 8.687798 ^C
ubuntu@pulseaudio:~$

If you do not have 6-channel audio output, you will hear audio on some of the channels only.

Let’s also test with an MP3 file, like that one from https://archive.org/details/testmp3testfile

ubuntu@pulseaudio:~$ sudo apt install mpg123
...
ubuntu@pulseaudio:~$ wget https://archive.org/download/testmp3testfile/mpthreetest.mp3
...
ubuntu@pulseaudio:~$ mplayer mpthreetest.mp3 
MPlayer 1.2.1 (Debian), built with gcc-5.3.1 (C) 2000-2016 MPlayer Team
...
AO: [pulse] 44100Hz 2ch s16le (2 bytes per sample)
Video: no video
Starting playback...
A:   3.7 (03.7) of 12.0 (12.0)  0.2% 

Exiting... (Quit)
ubuntu@pulseaudio:~$

All nice and loud!

Troubleshooting sound issues

AO: [pulse] Init failed: Connection refused

An application tries to connect to a PulseAudio server, but no PulseAudio server is found (either none autodetected, or the one we specified is not really there).

AO: [pulse] Init failed: Access denied

We specified a PulseAudio server, but we do not have access to connect to it. We need a valid cookie.

AO: [pulse] Init failed: Protocol error

You were trying as well to make the Unix socket work, but something was wrong. If you can make it work, write a comment below.

Testing with Firefox

Let’s test with Firefox!

ubuntu@guiapps:~$ sudo apt install firefox
...
ubuntu@guiapps:~$ firefox 
Gtk-Message: Failed to load module "canberra-gtk-module"

We get a message that the GTK+ module is missing. Let’s close Firefox, install the module and start Firefox again.

ubuntu@guiapps:~$ sudo apt-get install libcanberra-gtk3-module
ubuntu@guiapps:~$ firefox

Here we are playing a Youtube music video at 1080p. It works as expected. The Firefox session is separated from the host’s Firefox.

Note that the theming is not exactly what you get with Ubuntu. This is due to the container being so lightweight that it does not have any theming support.

The screenshot may look a bit grainy; this is due to some plugin I use in WordPress that does too much compression.

You may notice that no menubar is showing. Just like with Windows, simply press the Alt key for a second, and the menu bar will appear.

Testing with Chromium

Let’s test with Chromium!

ubuntu@guiapps:~$ sudo apt install chromium-browser
ubuntu@guiapps:~$ chromium-browser
Gtk-Message: Failed to load module "canberra-gtk-module"

So, chromium-browser also needs a libcanberra package, and it’s the GTK+ 2 package.

ubuntu@guiapps:~$ sudo apt install libcanberra-gtk-module
ubuntu@guiapps:~$ chromium-browser

There is no menubar and there is no easy way to get to it. The menu on the top-right is available though.

Testing with Chrome

Let’s download Chrome, install it and launch it.

ubuntu@guiapps:~$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
...
ubuntu@guiapps:~$ sudo dpkg -i google-chrome-stable_current_amd64.deb
...
Errors were encountered while processing:
 google-chrome-stable
ubuntu@guiapps:~$ sudo apt install -f
...
ubuntu@guiapps:~$ google-chrome
[11180:11945:0503/222317.923975:ERROR:object_proxy.cc(583)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files
[11180:11945:0503/222317.924441:ERROR:object_proxy.cc(583)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files
^C
ubuntu@guiapps:~$ sudo apt install upower
ubuntu@guiapps:~$ google-chrome

There are these two errors regarding UPower and they go away when we install the upower package.

Creating shortcuts to the container apps

If we want to run Firefox from the container, we can simply run

$ lxc exec guiapps -- sudo --login --user ubuntu firefox

and that’s it.

To make a shortcut, we create the following file on the host,

$ cat > ~/.local/share/applications/lxd-firefox.desktop[Desktop Entry]
Version=1.0
Name=Firefox in LXD
Comment=Access the Internet through an LXD container
Exec=/usr/bin/lxc exec guiapps -- sudo --login --user ubuntu firefox %U
Icon=/usr/share/icons/HighContrast/scalable/apps-extra/firefox-icon.svg
Type=Application
Categories=Network;WebBrowser;
^D
$ chmod +x ~/.local/share/applications/lxd-firefox.desktop

We need to make it executable so that it gets picked up and we can then run it by double-clicking.

If it does not appear immediately in the Dash, use your File Manager to locate the directory ~/.local/share/applications/

This is how the icon looks like in a File Manager. The icon comes from the high-contrast set, which now I remember that it means just two colors 🙁

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

I hope the tutorial was useful. We explain the commands in detail. In a future tutorial, we are going to try to figure out how to automate these!

26 comments

  • kelvin

    Hey thanks for this, really easy guide and super useful!

    I think I only had 3 deviations, don’t know whether others will find this useful… (host is ubuntu 17.04 with gnome3)

    I didn’t have an .Xauthority file in my hosts home directory so I had to create a symlink to /run/user/1000/gdm/Xauthority.
    When running paprefs on the host I needed to logout/login for changes to take affect.
    Maybe obvious but the pulseaudio package needs to be installed in the container for client/server audio routing to work.

  • Simos Xenitellis

    Thanks for the feedback.

    The Xauthority file should better be obtained from $XAUTHORITY (for the host). I updated the post to reflect this.
    In the container, I guess that it should check ~/.Xauthority as well, and therefore it would work.

    I wouldn’t think that the actual pulseaudio package needs to be installed in the container. Just like X11, it does not require the server to be installed or operational, but rather the necessary libraries that will be able to find the server.

    It is possible that with Ubuntu+GNOME Shell, those Pulseaudio libraries are not getting installed and require explicity installation. I’ll have to have a closer look on that.

  • G13Man

    thanks , being older and suffering migraines ,
    I am glad to see others figuring ways to use Linux and virtual machines and now container to make our computing safer and easier !

  • Luis

    Hello.. in the mapping the user section O am getting the following errors,

    lxc 20170529053238.561 ERROR lxc_conf – conf.c:run_buffer:405 – Script exited with status 1.
    lxc 20170529053238.561 ERROR lxc_conf – conf.c:lxc_setup:3890 – failed to run mount hooks for container ‘guiapps’.
    lxc 20170529053238.561 ERROR lxc_start – start.c:do_start:811 – Failed to setup container “guiapps”.
    lxc 20170529053238.561 ERROR lxc_sync – sync.c:__sync_wait:57 – An error occurred in another process (expected sequence number 3)
    lxc 20170529053238.611 ERROR lxc_start – start.c:__lxc_start:1346 – Failed to spawn container “guiapps”.
    lxc 20170529053239.218 ERROR lxc_conf – conf.c:run_buffer:405 – Script exited with status 1.
    lxc 20170529053239.218 ERROR lxc_start – start.c:lxc_fini:546 – Failed to run lxc.hook.post-stop for container “guiapps”.
    lxc 20170529053239.218 WARN lxc_commands – commands.c:lxc_cmd_rsp_recv:172 – Command get_cgroup failed to receive response: Connection reset by peer.
    lxc 20170529053239.218 WARN lxc_commands – commands.c:lxc_cmd_rsp_recv:172 – Command get_cgroup failed to receive response: Connection reset by peer.

    First I made a mistake, I executed the mapping inside the container, realised it and manually deleted from both files. then I got the error.
    I deleted the container, reproduced all the steps correctly and got the same error.

    Any ideas? (I am really new to LXD/LXC)

  • Luis

    It seems that a restart of the host solved the issue. Sorry I couldn’t find out what it was. Basically if manually edited “lxc config edit guiapps” and remove the rawid I was able to start the container, when I added it again, it didnt work .

    After restarting the host it was fixed

    WIth the audio I had a similar issue,, the playback was working on the host but not on the container. host restarting fixed the issue too.

  • Simos Xenitellis

    Can you provide the following:
    1. Which Linux distribution and version you are using? (run, for example, ‘lsb_release -a’)
    2. Which version of LXD you are running? (run ‘lxd –version’)

    For the container to have access to the X11 and Pulseaudio sockets, you need to have the correct ID mapping.
    Can you verify the User ID (UID) of your host that it is indeed 1000? (run ‘echo $UID’ and it should print 1000 or a similar number).

  • Luis

    Ok.. I started from scratch in a new container after restart. I didn’t get the error after mapping the user

    Audio I got the following error:

    ubuntu@guiapps:~$ speaker-test -c6 -twav

    speaker-test 1.1.0

    Playback device is default
    Stream parameters are 48000Hz, S16_LE, 6 channels
    WAV file(s)
    ALSA lib confmisc.c:768:(parse_card) cannot find card ‘0’
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
    ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
    ALSA lib confmisc.c:1251:(snd_func_refer) error evaluating name
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
    ALSA lib conf.c:4771:(snd_config_expand) Evaluate error: No such file or directory
    ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM default
    Playback open error: -2,No such file or directory
    ubuntu@guiapps:~$ exit
    logout

    Maybe alsa-utils is not enough. I installed mplayer (and its dependencies) inside the container and it worked.

    ubuntu@guiapps:~$ sudo apt install mplayer

  • Luis

    No LSB modules are available.
    Distributor ID: Ubuntu
    Description: Ubuntu 17.04
    Release: 17.04
    Codename: zesty

    lxd version 2.12

    However, As I sad, I could’t reproduce the error after the host restart
    UID in the host is 1149 however it is added correctly in /etc/subuid /etc/subgid
    cat /etc/subuid
    iotroot:100000:65536
    lxd:165536:65536
    root:165536:65536
    root:1149:1
    root:0:1

    I didnt have to add the gpu device as I have an Intel one, glxgears is working fine

    glxinfo -B
    name of display: :0
    display: :0 screen: 0
    direct rendering: Yes
    Extended renderer info (GLX_MESA_query_renderer):
    Vendor: Intel Open Source Technology Center (0x8086)
    Device: Mesa DRI Intel(R) Haswell Mobile (0x416)
    Version: 17.0.3
    Accelerated: yes
    Video memory: 1536MB
    Unified memory: yes
    Preferred profile: core (0x1)
    Max core profile version: 4.2
    Max compat profile version: 3.0
    Max GLES1 profile version: 1.1
    Max GLES[23] profile version: 3.1
    OpenGL vendor string: Intel Open Source Technology Center
    OpenGL renderer string: Mesa DRI Intel(R) Haswell Mobile
    OpenGL core profile version string: 4.5 (Core Profile) Mesa 17.0.3
    OpenGL core profile shading language version string: 4.50
    OpenGL core profile context flags: (none)
    OpenGL core profile profile mask: core profile

    OpenGL version string: 3.0 Mesa 17.0.3
    OpenGL shading language version string: 1.30
    OpenGL context flags: (none)

    OpenGL ES profile version string: OpenGL ES 3.1 Mesa 17.0.3
    OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.10

    as for the audio, mplayer is working , speaker-test is working in the host but not in the container. fair enough for me as I can get audio from the container

    ubuntu@guiapps:~$ speaker-test -c6 -twav

    speaker-test 1.1.0

    Playback device is default
    Stream parameters are 48000Hz, S16_LE, 6 channels
    WAV file(s)
    ALSA lib confmisc.c:768:(parse_card) cannot find card ‘0’
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
    ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
    ALSA lib confmisc.c:1251:(snd_func_refer) error evaluating name
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
    ALSA lib conf.c:4771:(snd_config_expand) Evaluate error: No such file or directory
    ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM default
    Playback open error: -2,No such file or directory
    ubuntu@guiapps:~$ exit
    logout

  • Luis

    I’m sorry i’m posting so many comments.

    One last thought is that I have to ‘lxc restart guiapps’ every time I log out/log in in my host account

    thank you very much.. amazing work!

  • Simos Xenitellis

    If you can get one application in the container to play audio, then audio works in the container. You mention that mplayer works in the container, right?
    The “speaker-test” utility may bypass Pulseaudio, and I think this is what you get here.

    Thanks for reporting this, because it gives a hint that ALSA utilities might not be configured properly to use Pulseaudio in Ubuntu 17.04.

    Here is the background for this:
    1. The Linux kernel has only one option for an audio subsystem, and this is ALSA (alsa-driver). Any audio we hear on Linux, comes from ALSA/alsa-driver.
    2. The ALSA software distribution comes with the ALSA utilities like aplay, arecord, speaker-test, that by default would access directly the ALSA/alsa-driver kernel module in order to play audio. This guide requires from the apps (in the LXD container) to go through Pulseaudio. It is not possible to divert audio without a layer like Pulseaudio.
    3. The ALSA utilities can get automatically configured to go through Pulseaudio (if Pulseaudio libraries are present), and this guide makes use of this feature.
    4. In your case, your ALSA utilities in the container did not get autoconfigured to go through Pulseaudio, therefore they were unable to work.

    I probably need to update the guide so that the audio command explicitly specifies PulseAudio. Therefore, if there is an issue, the command would fail with a more helpful message.

  • Simos Xenitellis

    Hi Luis!

    That article describes the same process, giving access from the container to the X11 Unix socket of the desktop.

    To make all this work on remote systems, it then introduces VirtualGL. VirtualGL would work here as well, though I have not tried it. I’ll try it out soon, unless someone else gives it a go sooner.

  • Satya

    For audio to WORK, apart from the above steps, I followed this article:

    https://bmullan.wordpress.com/2013/11/20/how-to-enable-sound-in-lxc-linux-containers/

    Let me write what I did after reading from article:

    1)
    find the ipaddress of the bride used (ifconfig -a)

    for me it is: 10.200.191.1

    lxdbr0: flags=4163 mtu 1500
    inet 10.200.191.1 netmask 255.255.255.0 broadcast 0.0.0.0
    inet6 fe80::20d7:8ff:fe72:cf53 prefixlen 64 scopeid 0x20
    inet6 fd42:5180:d828:a541::1 prefixlen 64 scopeid 0x0
    ether fe:8f:be:e7:4e:a4 txqueuelen 1000 (Ethernet)
    RX packets 92637 bytes 83560968 (83.5 MB)
    RX errors 0 dropped 0 overruns 0 frame 0
    TX packets 61822 bytes 52415104 (52.4 MB)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

    2)

    change ipaddress(the last subnet can be 0. so i used 10.200.191.0) and add the below lines to /etc/pulse/system.pa

    load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;10.200.191.0/24
    load-module module-zeroconf-publish

    3)

    ps -eaf|grep pulseaudio ; kill it; it will restart automatically

    4) now login to container: make sure your .profile/.bashrc has

    export PULSE_SERVER=tcp:10.200.191.1

    5) logout and login again

    6) now sound worked

  • Simos Xenitellis

    In Ubuntu (and probably in other distributions), Pulseaudio opens a Unix socket for the communication between processes that want access to audio. In this howto, the containers get access to both GUI and Audio through Unix sockets.

    Both X11 and Pulseaudio can be configured to be accessed by others through TCP/IP, though we try to avoid it for performance reasons. Those Unix sockets work find with local access (on same computer) but TCP/IP is typically required if there is a need to access remotely.

    Did you try to access Pulseaudio through the Unix socket and it did not work? We can work through it and figure out what went wrong. The most common issue is with permissions in ~/.config/ (in the container). You may need to mkdir ~/.config/pulse/, then run the lxc device command.

    It is good to have a variation of this guide that uses TCP/IP instead of Unix socket. That way, it would be possible to have the container run somewhere remote.

    Thanks for the instructions!

  • Asuranceturix

    I cannot reproduce the results you give in this tutorial. I am on Ubuntu 16.04 x64, with LXD 2.15. I follow all the steps, cutting and pasting then literally, but when I should get xclock to display correctly, I get this instead:

    ubuntu@guiapps:~$ xclock
    No protocol specified
    Error: Can’t open display: :0

    I have also noticed that, whereas /tmp/.X11-unix/X0 is persistents across restarts of the ‘guiapps’ container, the Xauthority file that should be found at /run/user/1000/gdm/Xauthority in the container does not reappear after a restart. When I try to set it again, lxd tells me the device already exists. However, even if I create it again, the results are the same:

    —- HOST —-
    dbd@zridi2:~$ lxc restart guiapps
    —- GUEST —-
    ubuntu@guiapps:~$ ls -l /tmp/.X11-unix/X0
    srwxrwxrwx 1 nobody nogroup 0 Jul 18 16:33 /tmp/.X11-unix/X0
    ubuntu@guiapps:~$ ls -l /run/user/
    total 0
    —- HOST —-
    dbd@zridi2:~$ lxc config device add guiapps Xauthority disk path=${XAUTHORITY} source=/home/${USER}/.Xauthority
    error: The device already exists
    dbd@zridi2:~$ lxc config device remove guiapps Xauthority
    Device Xauthority removed from guiapps
    dbd@zridi2:~$ lxc config device add guiapps Xauthority disk path=${XAUTHORITY} source=/home/${USER}/.Xauthority
    Device Xauthority added to guiapps
    —- GUEST —-
    ubuntu@guiapps:~$ ls -l /run/user/1000/gdm/Xauthority
    -rw——- 1 ubuntu ubuntu 102 Jul 17 16:45 /run/user/1000/gdm/Xauthority
    ubuntu@guiapps:~$ xclock
    No protocol specified
    Error: Can’t open display: :0

    What am I doing wrong?

    Thanks!

  • uti

    Nice tutorial. But one question remains for me. I use Ubuntu 16.04 and if i start Firefox inside the container Firefox (and all other appositions) have no menu. Unlike the other applications that are run on the host there is no menu showing when i hover over the top bar in unity. Is there a known solution?

  • bmullan

    @Simos

    I got the same results as Luis when trying to run:

    ubuntu@guiapps:~$ speaker-test -c6 -twav
    speaker-test 1.1.0

    Playback device is default
    Stream parameters are 48000Hz, S16_LE, 6 channels
    WAV file(s)
    ALSA lib confmisc.c:768:(parse_card) cannot find card ‘0’
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
    ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
    ALSA lib confmisc.c:1251:(snd_func_refer) error evaluating name
    ALSA lib conf.c:4292:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
    ALSA lib conf.c:4771:(snd_config_expand) Evaluate error: No such file or directory
    ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM default
    Playback open error: -2,No such file or directory

    I too could only get sound/audio if I changed the HOST /etc/pulse/system.pa to allow TCP connections from my LXDBR0 subnet
    as pointed out by Satya (where he used my Pulseaudio TCP solution).

    I am pretty sure I followed your config instructions correctly.

    Although I did notice in the section where you executed “speaker -test -c6 -twav” the prompt had changed from “ubuntu@guiapps”
    to
    ubuntu@pulseaudio:~$ speaker-test -c6 -twav (see above in your instructions)

    so maybe that was 2 different containers ?? and maybe one was configured differently than the other ??

    Brian

  • bmullan

    @Simos

    I went back and tested again but this time I deviated from your instructions of the settings for paprefs.

    In paprefs I selected the first 3 options:

    Enable network access to local sound devices
    Allow other machines on the LAN to discover local sound devices
    Don’t require authentication

    I then commented out the statements I had added to /etc/pulse/system.pa and restarted pulseaudio:

    $ killall pulseaudio # which kills pulseaudio but it automatically restarts & re-reads the new /etc/pulse/system.pa

    NOW… in the container sound works…!

    ubuntu@guiapps:~$ speaker-test -c6 -twav

    So I think your instructions just need to add those 2 extra settings in paprefs to work.

  • Tiger

    Hi Simos,
    thank you for your HOWTO with helpful explanations. Amazing job! Exactly what I was looking for. Now let’s install Steam… 🙂

    I’m running Ubuntu Desktop 17.04 as host and same as LXD container.
    I was running into the same problems with audio as some other poster here. bmullan’s trick did the job. audio works via IP now. Still, I would like to know what configurations are necessary to tunnel audio through Unix sockets… Simos can you include the additional setting in paprefs in the HOWTO too, for others to know?

    Simos, I came across an error, probably a typo in the section where you explain how to set up sockets for the display.
    I think the dollar sign at the path-part needs to go behind the parenthesis, like this…
    lxc config device add guiapps Xauthority disk path={$XAUTHORITY} source=/home/${USER}/.Xauthority

    Cheers, Tiger

  • Michael

    Hi Simos,

    Thank you for your guide. I noticed in a forum post that you’d used Steam to test this. I have Steam running in a container too with the host passing through xbox360 wireless controllers, it works really well.

    The problem I’m struggling with at the moment is to get Steam to recognise the addition of new wireless controllers during execution of Steam. If they are activated before starting Steam it picks them up, but if they come online while Steam is running they are not detected. Further, if an already working controller goes offline during Steam execution, Steam has to be restarted before it picks it up again. Wondering if you’ve come across this, or if you have any pointers for me in troubleshooting this?

    My current thinking is Steam might be monitoring udev events, and since udev doesn’t run inside the container it is missing the add/remove event. But I’m not really sure how to test this theory.

    Many thanks,
    Michael.

  • Simos Xenitellis

    Hi Michael!

    Which Linux kernel version do you have?
    I think that up to Linux 4.4 (default in Ubuntu 16.04), the device files for the controller where static files in /dev/, which was good.
    But newer versions of the kernel (that is, newer xpad.ko), the device files are created (and are removed) dynamically.

    See https://bugzilla.kernel.org/show_bug.cgi?id=115311
    It describes that the same problem exists with emulators as well.

    LXD supports USB passthrough; see https://stgraber.org/2017/03/27/usb-hotplug-with-lxd-containers/ See if you have get it working with USB passthrough. Please report back whether it works or not.

  • nick

    Hi everyone, I’m configuring LXD on Ubuntu 17.10 Final Beta. So that I can run from an external USB stick, a single, portable windows EXE that launches a GUI window.

    My understanding is that this version of Ubuntu uses Wayland and may not have X11 installed. How would we replace X11, with Wayland, in the commands?

    These are the commands I am referring to:
    $ lxc config device add wine-games X0 disk path=/tmp/.X11-unix/X0 source=/tmp/.X11-unix/X0
    $ lxc config device add wine-games Xauthority disk path=/home/ubuntu/.Xauthority source=/home/MYUSERNAME/.Xauthority

    Found here: https://blog.simos.info/how-to-run-wine-graphics-accelerated-in-an-lxd-container-on-ubuntu/

    I’m relatively new to Linux so don’t have any experience with most of the details in the commands. Thanks!

  • Simos Xenitellis

    In 17.10 it is possible to easily switch to X11, if required. This functionality is there to help NVidia users that may have trouble running Wayland. Therefore, that would be an option.

    I do not have an installation with Wayland yet. I know there is a compatibility X11 layer in Wayland, so that could be an option.
    The ideal way is to figure out what a native GUI app requires to display in Wayland, and try to provide that to the app in an LXD container.

  • Mangesh

    Hi,

    When I am trying to restart my guiapps, got below error.

    /home/max # lxc config set guiapps raw.idmap “both $UID 1000”
    root@lxdserver /home/max # lxc restart guiapps
    error: Error calling ‘lxd forkstart guiapps /var/lib/lxd/containers /var/log/lxd/guiapps/lxc.conf’: err=’Failed to run: /usr/bin/lxd forkstart guiapps /var/lib/lxd/containers /var/log/lxd/guiapps/lxc.conf: ‘
    lxc 20171018015541.497 ERROR lxc_conf – conf.c:run_buffer:416 – Script exited with status 1.
    lxc 20171018015541.497 ERROR lxc_conf – conf.c:lxc_setup:4039 – failed to run mount hooks for container ‘guiapps’.
    lxc 20171018015541.497 ERROR lxc_start – start.c:do_start:811 – Failed to setup container “guiapps”.
    lxc 20171018015541.497 ERROR lxc_sync – sync.c:__sync_wait:57 – An error occurred in another process (expected sequence number 3)
    lxc 20171018015541.570 ERROR lxc_start – start.c:__lxc_start:1358 – Failed to spawn container “guiapps”.
    lxc 20171018015542.130 ERROR lxc_conf – conf.c:run_buffer:416 – Script exited with status 1.
    lxc 20171018015542.130 ERROR lxc_start – start.c:lxc_fini:546 – Failed to run lxc.hook.post-stop for container “guiapps”.

    Try `lxc info –show-log guiapps` for more info

    Please let me know, how to resolve the issue.

    Regards,
    Mangesh

Leave a Reply

%d bloggers like this: