Update: A profile for running GUI apps in Incus Ubuntu containers with Ubuntu 24.04 as host.
Previous profiles are now here:
- Preliminary profile for Ubuntu 24.04
- Profile for Ubuntu 22.04 with kernel 6.5.0+
- Profile for Ubuntu 22.04 with older kernels
The main goal this time around was to simplify the profile as much as possible. This means instead of relying on cloud-init and systemd service to remount sockets, I created a bare bone incus_ct_gui.sh
script that you can push to the container and run once to get everything set up. You’ll find it in the Script section below.
Beside using this script, you’ll have to make a copy of the Xwaylandauth cookie on the host with a fixed name (it changes every time the host boots). This is necessary for X11 / xWayland sockets to work. See the What’s new section below.
This post is divided into four parts:
- Profile
- Script
- What’s new
- Troubleshooting
Profile
config:
raw.idmap: |-
uid 1000 1000
gid 1000 1000
description: Requires 'incus_ct_gui.sh' script. GUI Wayland and xWayland profile with pipewire and pulseaudio, shifting enabled for all socket.
devices:
gpu:
type: gpu
gid: 44
xwayland_socket:
type: disk
shift: true
source: /tmp/.X11-unix/X1
path: /mnt/.container_sockets/X1
xauthority_cookie:
type: disk
shift: true
source: /run/user/1000/.mutter-Xwaylandauth.copy
path: /mnt/.container_sockets/.mutter-Xwaylandauth.copy
wayland_socket:
type: disk
shift: true
source: /run/user/1000/wayland-0
path: /mnt/.container_sockets/wayland-0
pipewire_socket:
type: disk
shift: true
source: /run/user/1000/pipewire-0
path: /mnt/.container_sockets/pipewire-0
pipewire_manager_socket:
type: disk
shift: true
source: /run/user/1000/pipewire-0-manager
path: /mnt/.container_sockets/pipewire-0-manager
pulseaudio_socket:
type: disk
shift: true
source: /run/user/1000/pulse/native
path: /mnt/.container_sockets/native
Note 1: Profile requires creating a copy of Xwaylandauth cookie with the new name .mutter-Xwaylandauth.copy
, more on that in What’s new section below.
Note 2: Profile assumes that your user on the host has a UID and GID 1000, as well as the user in the container has a UID and GID 1000. You can check this using the id -u
and id -g
commands.
raw.idmap: |-
uid 1000 1000
gid 1000 1000
means:
raw.idmap: |-
uid <host_user_uid> <container_user_uid>
gid <host_user_gid> <container_user_gid>
Note 3: The GID for GPU is 44 because this is the GID for video group in Ubuntu containers.
Note 4: Profile adds access to the Wayland, xWayland, xwaylandauth cookie (also know as xauthority cookie), pipewire and pulseaudio sockets, which will be available in /mnt/.container_sockets/ folder and have to be linked to their proper locations using a script.
Script
You can name this script anyway you want, for example incus_ct_gui.sh. It will:
- Install pulseaudio-utils and dbus-user-session packages
- Add default container user to the video and render groups (this is not strictly necessary, but it helps with hardware video decoding, etc.)
- Set up environment variables in the
$HOME/.profile
file - Add commands to the
$HOME/.profile
file that link sockets from /mnt/.container_sockets/ folder to their proper locations (idea come from this post by @catfish)
#!/bin/bash
#
# This script should be run inside Incus containers. It links sockets added by the 'gui' profile, from ${mnt_dir} to their proper locations on every login, installs a couple of packages and sets up necessary environment variables.
readonly mnt_dir="/mnt/.container_sockets"
readonly run_dir="$XDG_RUNTIME_DIR"
readonly tmp_dir="/tmp/.X11-unix"
function modify_profile() {
# Add socket linking commands to the "$HOME/.profile" file, copy Xwaylandauth cookie and set up necessary environment variables.
# Now on every user login sockets from ${mnt_dir} will be linked to their proper locations.
cat << EOF >> "$HOME/.profile"
[[ ! -d "${run_dir}/pulse" ]] && mkdir -m 700 "${run_dir}/pulse"
[[ -S "${mnt_dir}/native" && -d "${run_dir}/pulse" && ! -e "${run_dir}/pulse/native" ]] && ln -s "${mnt_dir}/native" "${run_dir}/pulse/native"
[[ -S "${mnt_dir}/pipewire-0" && ! -e "${run_dir}/pipewire-0" ]] && ln -s "${mnt_dir}/pipewire-0" "${run_dir}/pipewire-0"
[[ -S "${mnt_dir}/pipewire-0-manager" && ! -e "${run_dir}/pipewire-0-manager" ]] && ln -s "${mnt_dir}/pipewire-0-manager" "${run_dir}/pipewire-0-manager"
[[ -S "${mnt_dir}/wayland-0" && ! -e "${run_dir}/wayland-0" ]] && ln -s "${mnt_dir}/wayland-0" "${run_dir}/wayland-0"
[[ -S "${mnt_dir}/X1" && ! -e "${tmp_dir}/X1" ]] && ln -s "${mnt_dir}/X1" "${tmp_dir}/X1"
[[ -f "${mnt_dir}/.mutter-Xwaylandauth.copy" && ! -e "${run_dir}/.mutter-Xwaylandauth.copy" ]] && cp "${mnt_dir}/.mutter-Xwaylandauth.copy" "${run_dir}/.mutter-Xwaylandauth.copy"
export WAYLAND_DISPLAY=wayland-0
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export DISPLAY=:1
export XAUTHORITY=${run_dir}/.mutter-Xwaylandauth.copy
EOF
}
function main() {
# Add user to the video and render groups.
sudo usermod -a -G video,render "$LOGNAME"
sudo apt update && sudo apt upgrade -y && sudo apt install -y pulseaudio-utils dbus-user-session
modify_profile
}
main
Push the script to the container, log in, execute it (for containers other than Ubuntu, change the default username) and reboot the container:
incus file push --gid=1000 --uid=1000 --mode=0744 /<path>/incus_ct_gui.sh <instance_name>/home/ubuntu/
incus exec <instance_name> -- sudo --user ubuntu --login
./incus_ct_gui.sh
Now you can start apps like this:
incus exec <instance_name> -- sudo --login --user ubuntu bash -ilc "<command>"
What’s new
If you previously used Ubuntu 22.04 profile, you’ll notice three things in 24.04 version:
- X11 / xWayland sockets require Xwaylandauth cookie (also know as Xauthority cookie)
- PipeWire is the new multimedia framework for Ubuntu and uses two new sockets
- I use both
shift: true
andraw.idmap
for shifting at the same time
Xauthority cookie is a bit tricky, because it has a random suffix that changes every time the host boots, for example .mutter-Xwaylandauth.77CDN2
. But we can use Startup Applications on the host with the following entry to create a copy of that cookie with a fixed name on every boot:
bash -c "cp -f $XAUTHORITY $XDG_RUNTIME_DIR/.mutter-Xwaylandauth.copy"
Profile will make that copy accessible inside container.
Adding support for PipeWire requires two new sockets on top of previous PulseAudio: pipewire-0
and pipewire-0-manager
.
Using both shift: true
and raw.idmap
at the same time eliminates a lot of weird errors when using snap packages, flatpaks and sometimes even regular debs. From my testing shift: true takes precedent over raw.idmap and using both at the same time should have no negative consequences for other devices that use shifting.
You can test that by creating a container with a purposefully wrong raw.idmap and sharing a folder with it. Look at permissions for that folder inside the container. Now set the shift: true for that folder and notice the change in permissions.
Troubleshooting
Sockets
Run those commands inside a container to check if all sockets are where they should be:
ll /tmp/.X11-unix/X?
ll /run/user/*/
ll /run/user/*/pulse/
xWayland
If you have problems with running xWayland / X11 apps just after the host boots, try
launching any X11 app on host, for example xclock
.
If you still have problems with apps using xWayland, try X11 socket instead. You can add it to the container like this:
incus config device add <instance_name> x11_socket disk shift=true source=/tmp/.X11-unix/X0 path=/mnt/.container_sockets/X0
Then in .profile file inside container change DISPLAY environment variable to export DISPLAY=:0
, then add this line and reboot it:
[[ -S "/mnt/.container_sockets/X0" && ! -e "/tmp/.X11-unix/X0" ]] && ln -s "/mnt/.container_sockets/X0" "/tmp/.X11-unix/X0"
You can completely disable xWayland socket by overriding it with this command:
incus config device add <instance_name> xwayland_socket none
Shifting
To change raw.idmap, set a new value with this command:
printf "uid $(id -u) 1000\ngid $(id -g) 1000" | incus config set <instance_name> raw.idmap -
To unset raw.idmap, use this command:
incus config unset <instance_name> raw.idmap
Audio
To test audio, run pactl info
command couple of times, fast. If it shows Connection failure: Access denied at least once, then try copying pulseaudio cookie from host ~/.config/pulse/cookie
to the container:
incus file push -p --mode=600 --gid=1000 --uid=1000 ~/.config/pulse/cookie <instance_name>/home/ubuntu/.config/pulse/
If you still have problems with audio, you may try disabling 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