Run accelerated GUI apps in LXC containers
For instance in this post by LXC developer Stephane Graber you can see how he uses this to run Google Chrome in a container locally. He uses unprivileged containers for the post.
Unprivileged containers are an exciting upstream feature of LXC but current dependencies limit them to latest versions of Ubuntu Trusty for now. Please see our using unprivileged containers guide for more.
We are going to show you how to run GUI apps in both normal and unprivileged containers. Here is a screencast of the LXC GUI container in action.
Stephane's Google Chrome example basically sets up Google Chrome in an Ubuntu container, gives the container access to the X server, and setups a script to launch Chrome. Its also exports pulse audio. Pulseaudio can be fussy and involved and may not work in all environments and distributions.
Conceptually you are exporting the dri, video and audio devices into the container and using these to run your apps. So there is no need to install X or drivers in the container. The GUI apps will use the hosts display and audio. Nice!
You can install something like Wine or Steam in a container and use it to run Windows Apps and Games isolated in their own container. Or like Stephane you can run apps like Chrome in a container to isolate it from the rest of your system. This makes sense for a number of use cases.
You can launch X apps from within the container and they will open up in a new window in your host. Or use a script on the host that executes the X app in the container and gives you a window, behaving much like any other GUI app on the system.
If you are new to LXC please head to our LXC getting started guide before trying this.
For those who are on Ubuntu Trusty and above we have a separate section and download below on how to run GUI apps in unprivileged containers.
Running GUI apps in normal containers
tar -xpJf lxcgui.tar.xz -C /var/lib/lxc --numeric-owner
Or if you are using the flockport utility
flockport get lxcgui
Before you can start the container let's take a quick look at what is making the GUI in the container possible. Head to the /var/lib/lxc/lxcgui folder and open the config file in your favorite editor. You will see the following 5 lines, and this is all you need to create you own LXC GUI container.
lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir lxc.mount.entry = /tmp/.X11-unix tmp/.X11-unix none bind,optional,create=dir lxc.mount.entry = /dev/video0 dev/video0 none bind,optional,create=file lxc.hook.pre-start = /var/lib/lxc/lxcgui/setup-pulse.sh
As you can see the configuration above line 1-4 are mounting the dri, snd, video devices in the container along with the /tmp/.X11-unix folder. This is all it takes to run accelerated GUI apps in your container.
Simples! For the GUI yes and it works smoothly. The audio is a bit involved, pulseaudio may fail weirdly on different systems. We have tested this on Debian Wheezy and Ubuntu Trusty hosts.
You will notice on the last line we are loading a setup-pulse.sh script. This is for audio. Let's have a look at what it does. Open the setup-pulse script located in /var/lib/lxc/lxcgui folder.
nano setup-pulse.sh
#!/bin/sh PULSE_PATH=/var/lib/lxc/lxcgui/rootfs/home/ubuntu/.pulse_socket if [ ! -e "$PULSE_PATH" ] || [ -z "$(lsof -n $PULSE_PATH 2>&1)" ]; then sudo -u username -i pactl load-module module-native-protocol-unix auth-anonymous=1 \ socket=$PULSE_PATH fi
As you can see the script exports pulseaudio via a socket into the container. But trying to run the pactl command in the script as root is going to fail. That's why there is a sudo switch username. Change the 'username' in the script to your normal username on your host.
Start the container
lxc-start -n lxcgui -d
The container should start without any errors. If you get any errors it will most likely be because of pulseaudio permission issues.
We will explore pulseaudio troubleshooting solutions at the end of this article. In the interim open the container config file and comment out the lxc.hook.pre-start line, so we can proceed with the GUI.
#lxc.hook.pre-start = /var/lib/lxc/lxcgui/setup-pulse.sh
Start the container after commenting out the line.
Now that the container is running we can use lxc-attach to get into it. However since we are going to run most of the GUI apps as the ubuntu user in the container, its better to ssh into the container. To do that first get the container IP
lxc-ls -f
Now let's ssh as user ubuntu into the container. In my case the container IP is 10.0.3.107. Like with all Flockport ubuntu containers the pass for the ubuntu user is ubuntu Please change this after first login with the passwd ubuntu command.
Once you are logged into the container we are ready to run some GUI apps. The container has Chromium and some utilities to test. To run GUI we first need to set some environmental variables. On the command line run the following 2 commands
export DISPLAY=:0 export PULSE_SERVER=/home/ubuntu/.pulse_socket
Please note: The default display is usually :0. If you have changed this for any reason then please change the variable as suitable.
Now let's run a couple of apps.
chromium-browser --disable-setuid-sandbox
This should launch a Chromium browser window on your host. Browse to Youtube and check if its working as expected with audio. This is actually running in your container. Chrome and Chromium do not play well with container namespaces and that's why we need to use the disable-setuid option.
Let's test an mp3
audacious
This should open up audacious. Test an mp3 to confirm audio working.
Now let's do an elementary 3D GUI test
glxgears
This should give you a frame rate identical to your host, so there is no overhead running your 3D apps in the container. You can run Steam in an LXC container and see for yourself.
You can basically run any GUI app including a desktop environment, Wine Windows apps, 3D games with near zero performance penalty and additional flexibility. Awesome!
Using another X session
You can also start a separate X instance on another VT and use that for GUI apps running on your container. For instance start a separate X instance with 'startx' from another VT, and in the container change the 'export DISPLAY=:0' to 'export DISPLAY=:1' . This will launch all GUI apps started on the container on your separate X window.
Launching GUI container app with a script directly from the host
It would be a hassle to start the container, log into it, set environmental variables every time you want to run a GUI app. So you can use a script to launch the GUI app directly from the container.
For instance the script below from Stephane's Chrome guide automatically starts the container, setups environmental variables and launches Google Chrome in the container.
You can set up a link to it on your desktop to execute the script. Chrome launches in about 3 seconds from the container. To use it to launch other apps just change the CMD-LINE value.
#!/bin/sh CONTAINER=lxcgui CMD_LINE="chromium-browser --disable-setuid-sandbox $*" STARTED=false if ! lxc-wait -n $CONTAINER -s RUNNING -t 0; then lxc-start -n $CONTAINER -d lxc-wait -n $CONTAINER -s RUNNING STARTED=true fi PULSE_SOCKET=/home/ubuntu/.pulse_socket lxc-attach --clear-env -n $CONTAINER -- sudo -u ubuntu -i \ env DISPLAY=$DISPLAY PULSE_SERVER=$PULSE_SOCKET $CMD_LINE if [ "$STARTED" = "true" ]; then lxc-stop -n $CONTAINER -t 10 fi
Running GUI apps in unprivileged containers
Please see our Using unprivileged containers guide to learn more
Please go through the guide above for general overview of running GUI apps in containers as we are only going to cover the differences for unprivileged containers below. Everything more or less remains the same apart from the changes below.
- Browse to the Flockport container section and download the LXC GUI unprivileged container. Once downloaded untar it to your user's LXC folder which is usually in/home/username/.local/share/lxc
sudo tar -xpJf lxcgui.tar.xz -C /home/username/.local/share/lxc --numeric-owner
- Head to the container folder, and open the container config in your favourite text editor. Notice the lxc.id_map entries. UID and GID mapping is required for unprivileged containers
lxc.id_map = u 0 100000 1000 lxc.id_map = g 0 100000 1000 lxc.id_map = u 1000 1000 1 lxc.id_map = g 1000 1000 1 lxc.id_map = u 1001 101001 64535 lxc.id_map = g 1001 101001 64535
- The lxc.hook.pre-start location in the container's config file also changes from/var/lib/lxc to your user's LXC folder
lxc.hook.pre-start = /home/username/.local/share/lxc/lxcgui/setup-pulse.sh
- The setup-pulse.sh script in the container folder does not need to change user.
#!/bin/sh PULSE_PATH=$LXC_ROOTFS_PATH/home/ubuntu/.pulse_socket if [ ! -e "$PULSE_PATH" ] || [ -z "$(lsof -n $PULSE_PATH 2>&1)" ]; then pactl load-module module-native-protocol-unix auth-anonymous=1 \ socket=$PULSE_PATH fi
- Go to the Flockport lxcgui container folder. The permission for the Ubuntu user is 1000:1000. It's already set in the Flockport container but when you want to create your own unprivileged GUI container remember to run this chown command
sudo chown -R 1000:1000 rootfs/home/ubuntu
That's all. The container should work without issues.
Troubleshooting pulseaudio issues
In many cases the pactl script will fail to run as root and as the user. There could be permission problems in your /var/lib/lxc folder or the container preventing pactl running as your user and creating the .pulse_socket.
One work around is to change the .pulse_socket location in the setup pulse script to the hosts /tmp folder and bind mount that in the lxcgui container config script. This may help avoid a lot of permission issues for pulseaudio.
For instance let your local user (not root) create an .lxc folder in /tmp.
mkdir /tmp/.lxc
Now in the setup-pulse.sh change the socket location or PULSE_PATH value to /tmp/.lxc/.pulse-socket like below, and change username to your username.
#!/bin/sh PULSE_PATH=/tmp/.lxc/.pulse_socket if [ ! -e "$PULSE_PATH" ] || [ -z "$(lsof -n $PULSE_PATH 2>&1)" ]; then sudo -u username -i pactl load-module module-native-protocol-unix auth-anonymous=1 \ socket=$PULSE_PATH fi
The add an entry to the container's config bind mounting /tmp/.lxc to the container like below.
lxc.mount.entry = /tmp/.lxc tmp/.lxc none bind,optional,create=dir
Then if you want a run a app from within a container without using a launch script then you would export pulse-server to the tmp location ie
export PULSE_SERVER=/tmp/.lxc/.pulse-socket
This should work in most cases.