Disclaimer: This how to is intended for advanced users. You should be able to understand what commands are doing, before copy-pasting and executing them.
Chromium is picked as an application for demonstration, because it’s one I know where it’s possible to easily to select and force application to use Wayland or X11 backend.
It also provides chrome://gpu/
to some details to check things, and it can run WebGL also with software rendering.
Step one - direct wayland connection
Starting simple:
- create base image with Chromium,
- run container:
- pass host’s Wayland socket,
- application connects to host’s Wayland directly through socket.
Create base image chromium-only.Dockerfile
:
FROM debian:12
RUN apt-get update && \
apt-get install -y chromium && \
rm -rf /var/lib/apt/lists/*
# Set non-root user and group
ARG user=appuser
ARG group=appuser
ARG uid=1000
ARG gid=1000
RUN groupadd -g ${gid} ${group} -f
RUN useradd -u ${uid} -g ${group} -m ${user}
USER ${uid}:${gid}
Build the image with docker or podman:
docker build . \
--file chromium-only.Dockerfile \
--build-arg uid=$(id -u) \
--build-arg gid=$(id -g) \
-t debian-with-chromium
podman build . \
--file chromium-only.Dockerfile \
--build-arg uid=$(id -u) \
--build-arg gid=$(id -g) \
-t debian-with-chromium
Run Chromium in the container:
docker run -e XDG_RUNTIME_DIR=/tmp \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
debian-with-chromium \
chromium --no-sandbox --ozone-platform=wayland
podman run -e XDG_RUNTIME_DIR=/tmp \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
--userns=keep-id:uid=$(id -u),gid=$(id -g) \
debian-with-chromium \
chromium --no-sandbox --ozone-platform=wayland
Notes:
- Chromium’s sandbox requires extra capabilities, that are not given to container, so running with
--no-sandbox
, - podman needs extra configuration to not get permission denied, which can be done with
--userns=keep-id:uid=$(id -u),gid=$(id -g)
, - running with
--ozone-platform=x11
will fail.
Step two - through sommelier (same container)
Add sommelier to the mix:
- create base image with Chromium & sommelier installed,
- run container:
- pass host’s Wayland socket,
- sommelier connects to host’s Wayland,
- applications connects to sommelier.
FROM debian:12
RUN apt-get update && \
apt-get install -y git meson libwayland-bin python3-jinja2 build-essential pkg-config cmake libgbm-dev libdrm-dev libpixman-1-dev librust-wayland-client-dev xcb libxcb-render-util0-dev libxcb-composite0-dev libxkbcommon-dev && \
apt-get install -y chromium && \
rm -rf /var/lib/apt/lists/*
RUN mkdir /tmp-sommelier-build && \
cd /tmp-sommelier-build && \
git clone https://chromium.googlesource.com/chromiumos/platform2 --depth 1 && \
cd platform2/vm_tools/sommelier/ && \
meson setup build -Dwith_tests=false && \
meson compile -C build && \
meson install -C build && \
cd / && \
rm -rf /tmp-sommelier-build
RUN apt-get update && \
apt-get install -y xwayland && \
rm -rf /var/lib/apt/lists/*
# Set non-root user and group
ARG user=appuser
ARG group=appuser
ARG uid=1000
ARG gid=1000
RUN groupadd -g ${gid} ${group} -f
RUN useradd -u ${uid} -g ${group} -m ${user}
USER ${uid}:${gid}
docker build . \
--file chromium-sommelier.Dockerfile \
--build-arg uid=$(id -u) \
--build-arg gid=$(id -g) \
-t debian-with-chromium-sommelier
podman build . \
--file chromium-sommelier.Dockerfile \
--build-arg uid=$(id -u) \
--build-arg gid=$(id -g) \
-t debian-with-chromium-sommelier
Run Chromium in the container through sommelier - Wayland:
docker run -e XDG_RUNTIME_DIR=/tmp \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
debian-with-chromium-sommelier \
sommelier --display=$WAYLAND_DISPLAY --noop-driver chromium --no-sandbox --ozone-platform=wayland
podman run -e XDG_RUNTIME_DIR=/tmp \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
--userns=keep-id:uid=$(id -u),gid=$(id -g) \
debian-with-chromium-sommelier \
sommelier --display=$WAYLAND_DISPLAY --noop-driver chromium --no-sandbox --ozone-platform=wayland
Run Chromium in the container through sommelier providing XWayland proxy for X11 clients:
docker run -e XDG_RUNTIME_DIR=/tmp \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
debian-with-chromium-sommelier \
sommelier --display=$WAYLAND_DISPLAY --noop-driver -X --xwayland-path=/usr/bin/Xwayland chromium --no-sandbox --ozone-platform=x11
podman run -e XDG_RUNTIME_DIR=/tmp \
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
--userns=keep-id:uid=$(id -u),gid=$(id -g) \
debian-with-chromium-sommelier \
sommelier --display=$WAYLAND_DISPLAY --noop-driver -X --xwayland-path=/usr/bin/Xwayland chromium --no-sandbox --ozone-platform=x11
Check outputs of wayland-info
:
docker run ... debian-with-chromium-sommelier \
wayland-info
docker run ... debian-with-chromium-sommelier \
sommelier --display=$WAYLAND_DISPLAY --noop-driver wayland-info
Notes:
- development packages to build sommelier not required to run are present in the final image (I’ll eventually iterate on this and clean up),
- uses
--noop-driver
which simply forwards buffers to host without any special processing.
Step three - separate sommelier and application containers
Sorry, not achieved.
The idea is to have separate containers:
- container for parent sommelier proxying Wayland to apps,
- access to host’s Wayland through socket,
- container with app
- no access to host’s Wayland,
- access to sommelier through socket.
This seems to not be possible with the latest version of sommelier, and I have not tried previous versions.
The noop
shm and data driver was removed with this commit in 2021.
Final step - docker compose
With the limitations of not doing separation,…
Create docker-compose.yaml
:
services:
chromium:
image: debian-with-chromium-sommelier
build:
context: .
dockerfile: chromium-sommelier.Dockerfile
args:
uid: "$USER_UID"
gid: "$USER_GID"
environment:
XDG_RUNTIME_DIR: /tmp
WAYLAND_DISPLAY: $WAYLAND_DISPLAY
volumes:
- type: bind
source: $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY
target: /tmp/$WAYLAND_DISPLAY
command: sommelier --display=$WAYLAND_DISPLAY --noop-driver chromium --no-sandbox --ozone-platform=wayland
Run docker compose:
USER_UID=$(id -u) USER_GID=$(id -g) docker compose up
# if rebuild is needed:
USER_UID=$(id -u) USER_GID=$(id -g) docker compose up --build
Create podman-compose.yaml
:
x-podman:
in_pod: false
services:
chromium:
image: debian-with-chromium-sommelier
build:
context: .
dockerfile: chromium-sommelier.Dockerfile
args:
uid: "$USER_UID"
gid: "$USER_GID"
userns_mode: "keep-id:uid=$USER_UID,gid=$USER_GID"
environment:
XDG_RUNTIME_DIR: /tmp
WAYLAND_DISPLAY: $WAYLAND_DISPLAY
volumes:
- type: bind
source: $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY
target: /tmp/$WAYLAND_DISPLAY
command: sommelier --display=$WAYLAND_DISPLAY --noop-driver chromium --no-sandbox --ozone-platform=wayland
Run podman compose:
USER_UID=$(id -u) USER_GID=$(id -g) podman-compose up
# if rebuild is needed:
USER_UID=$(id -u) USER_GID=$(id -g) podman-compose up --build
Security notes
While this provides isolation through containers, I have NOT done a security research.
At minimum, the attack surface is following:
- exploits (if present) in docker or podman container technology (or misconfiguration),
- exploits (if present) in host’s Wayland compositor that are executable through socket connection,
- exploits (if present) in Wayland’s protocols that can leak things.
Notes:
- sommelier is maybe redundant, because socket to host’s wayland is present in the application’s container.
If the purpose is not security, but work organization into some reproducible workspaces, setups, throwaway experiment environments, running apps on top of different base distributions…. then this approach should be fine.
For full confidence in isolation (against malware), go with Qubes OS.
References:
Things mentioned:
- sommelier - Wayland proxy compositor.
Used sources:
- answer on Unix & Linux Stack Exchange - bind-mounting essential wayland pieces.
- crosvm’s docs on wayland - sommelier build steps.