I made a profile for running GUI apps in an Incus / LXD container. It supports wayland, X11 and pulseaudio. This profile is based on work of Justin Ludwig, thank you!
https://blog.swwomm.com/2022/08/lxd-containers-for-wayland-gui-apps.html
Profile has been tested using:
- both Incus and LXD
- Ubuntu 22.04 as a host
- Ubuntu 22.04 container images, both from community repository
images:ubuntu/jammy/cloud
and official Canonical repositoryubuntu:22.04
This post is divided into four parts:
- Profile
- Explanation
- Testing
- Troubleshooting
1. Profile
Note 1: those Ubuntu images have a default ubuntu user, which is hard-coded in the profile.
Note 2: profile adds some environment variables to .profile file inside container. Every time you change a profile using command incus profile edit <profile_name>
it will be applied once again to all containers using it and therefore those environment variables will be duplicated in .profile files. This doesn’t break anything, just be aware of that.
config:
raw.idmap: both 1000 1000
security.nesting: "true"
user.user-data: |
#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
packages:
- pulseaudio-utils
write_files:
- path: /usr/local/bin/mystartup.sh
permissions: 0755
content: |
#!/bin/sh
uid=$(id -u)
run_dir=/run/user/$uid
mkdir -p $run_dir && chmod 700 $run_dir && chown $uid:$uid $run_dir
ln -sf /mnt/.container_wayland_socket $run_dir/wayland-0
mkdir -p $run_dir/pulse && chmod 700 $run_dir/pulse && chown $uid:$uid $run_dir/pulse
ln -sf /mnt/.container_pulseaudio_socket $run_dir/pulse/native
tmp_dir=/tmp/.X11-unix
mkdir -p $tmp_dir
ln -sf /mnt/.container_x11_socket $tmp_dir/X0
- path: /usr/local/etc/mystartup.service
content: |
[Unit]
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mystartup.sh
[Install]
WantedBy=default.target
runcmd:
- mkdir -p /home/ubuntu/.config/systemd/user/default.target.wants
- ln -s /usr/local/etc/mystartup.service /home/ubuntu/.config/systemd/user/default.target.wants/mystartup.service
- ln -s /usr/local/etc/mystartup.service /home/ubuntu/.config/systemd/user/mystartup.service
- chown -R ubuntu:ubuntu /home/ubuntu
- echo 'export WAYLAND_DISPLAY=wayland-0' >> /home/ubuntu/.profile
- echo 'export XDG_SESSION_TYPE=wayland' >> /home/ubuntu/.profile
- echo 'export QT_QPA_PLATFORM=wayland' >> /home/ubuntu/.profile
- echo 'export DISPLAY=:0' >> /home/ubuntu/.profile
description: GUI Wayland and X11 profile with pulseaudio
devices:
gpu:
type: gpu
gid: 44
wayland_socket:
source: /run/user/1000/wayland-0
path: /mnt/.container_wayland_socket
type: disk
x11_socket:
source: /tmp/.X11-unix/X0
path: /mnt/.container_x11_socket
type: disk
pulseaudio_socket:
source: /run/user/1000/pulse/native
path: /mnt/.container_pulseaudio_socket
type: disk
The easiest way to use a profile like that is to copy it into a text file, then create an empty profile in Incus:
incus profile create <profile_name>
and update that profile with the file’s content:
incus profile edit <profile_name> < /<path>/<file_name>
2. Explanation
For an in depth explanation of how this profile creates a script and startup systemd service that links all sockets to their usual location inside the container, please read Justin’s post at his blog. Here I’ll explain only tweaks I made:
- Installing
pulseaudio-utils
package will create pulseaudio cookie inside container. - I added X11 socket for apps that don’t use Wayland yet.
- All sockets are shared as
type: disk
, nottype: proxy
device. - Adding GPU with
gid: 44
enables GPU hardware video acceleration in containers. See Testing section below. - Key
security.nesting: "true"
is for Steam, Docker, etc.
3. Testing
Launch container using this profile:
incus launch images:ubuntu/jammy/cloud -p default -p <profile_name> <container_name>
Now login into your container:
incus exec <container_name> -- sudo --user ubuntu --login
Pulseaudio socket is called native
and you can find it at /run/user/1000/pulse/
. Wayland socket wayland-0
is in /run/user/1000/
folder, and X11 socket X0
is in /tmp/.X11-unix/
folder. On host in /tmp/.X11-unix/
folder you will find also Xwayland socket as X1
.
All those sockets inside container should be visible at /mnt/
and linked into proper folders. Run those command to check if that’s true:
ll /mnt/.container*
ll /tmp/.X11-unix/X?
ll /run/user/*/wayland-?
ll /run/user/*/pulse/native
Check if most important environment variables are set, mainly WAYLAND_DISPLAY=wayland-0
and DISPLAY=:0
:
printenv | grep -i display
Check if your user inside container is part of the video group using groups
command. Then see if video render is owned by this group using ll /dev/dri/
command. Output should show root video
(without gid: 44
it would be root root
):
crw-rw---- 1 root video 226, 0 Nov 15 08:41 card0
crw-rw---- 1 root video 226, 128 Nov 15 08:41 renderD128
To test pulseaudio run pactl info
command couple of times. If it shows Connection failure: Access denied at least once, then see Troubleshooting section.
For a final test, install Chrome, then run it in Wayland or X11 mode and watch any Youtube video with sound:
sudo apt update && sudo apt upgrade -y
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ~/google-chrome-stable_current_amd64.deb
sudo apt install libegl1
Without libegl1
package, Chrome will complain. Depending on which ubuntu image you used to create the container, Chrome will spill out some errors, but should work perfectly fine. To run Chrome in Wayland mode, use this command:
google-chrome --enable-features=UseOzonePlatform --ozone-platform=wayland
To run it in X11 mode, simply use this command:
google-chrome
Now on the host you can run xlsclients
and see if Chrome shows up when run in X11 mode and if it’s absent when run in Wayland mode.
4. Troubleshooting
If pulseaudio test pactl info
showed Connection failure: Access denied, then try copying pulseaudio cookie from host ~/.config/pulse/cookie
to container:
incus file push -p --mode=600 --gid=1000 --uid=1000 ~/.config/pulse/cookie <container_name>/home/ubuntu/.config/pulse/
If you still have problems with pulseaudio you may try to disable shared memory inside container in /etc/pulse/client.conf
config file manually or with this command:
sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf
If Chrome doesn’t start in X11 mode, you can try changing in profile socket X0
to X1
and corresponding environment variable from DISPLAY=:0
to DISPLAY=:1
.