
Windows: pass the X11 socket through #
environment:
DISPLAY: ${DISPLAY:-:0}
QT_X11_NO_MITSHM: 1
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw| Piece | What it does |
|---|---|
/tmp/.X11-unix mount |
the X server listens on a Unix socket here (X0 for :0); mounting it gives the container a line to it |
DISPLAY |
tells X clients (GLFW, Qt) which display to use; defaults to :0 |
QT_X11_NO_MITSHM=1 |
stops Qt — OpenCV’s imshow backend — from using MIT-SHM, which fails across containers and gives blank or garbled windows |
xhost +local:root (in setup_host.sh) |
the X server refuses untrusted clients; this admits local root, which is who the containers run as |
On Wayland #
Wayland sessions (KDE Plasma, GNOME) still run XWayland on :0, and both windows open through it —
the recording above was made exactly that way. Running MuJoCo natively on a Wayland host, GLFW may
warn Wayland: The platform does not provide the window position; it’s harmless.
GPU: /dev/dri
#
devices:
- /dev/dri:/dev/dri/dev/dri holds the Direct Rendering Infrastructure nodes (card*, renderD*). With them, Mesa in the
container renders on the real GPU; without them it falls back to CPU software rendering and the viewer
crawls.
/dev/dri is all you need — Mesa drives the GPU from inside the container. This project was developed on
Intel UHD Graphics (Comet Lake).
/dev/dri. Install the NVIDIA Container Toolkit, then add a
GPU reservation (deploy.resources.reservations.devices with capabilities: [gpu], or
runtime: nvidia) and NVIDIA_DRIVER_CAPABILITIES=all to the services.
MUJOCO_GL=glfw selects MuJoCo’s windowed backend, which the interactive viewer needs; egl and
osmesa are for headless offscreen rendering.
MediaPipe gets /dev/dri too. On the development host, its log showed that even the CPU hand-tracking
graph opens an EGL context on the GPU at startup
(gl_context_egl.cc … Successfully initialized EGL … Mesa Intel(R) UHD Graphics) — which may matter for
performance (Part 19).
Webcam: /dev/video0
#
devices:
- /dev/video0:/dev/video0
environment:
CAMERA_INDEX: 0- OpenCV’s V4L2 backend opens
/dev/video<CAMERA_INDEX>. Verified inside the container: V4L2, 640×480, 30 fps, YUYV. - Many webcams expose two nodes —
/dev/video0for frames,/dev/video1for metadata. Use the first. - Another camera: map it and match the index, e.g.
/dev/video2withCAMERA_INDEX: 2. v4l-utilsis in the image:docker compose exec vision_tracker v4l2-ctl --list-formats-ext.- One process per camera. Stop the standalone script and close browser tabs using the webcam first.
setup_host.sh
#
./setup_host.shIt runs xhost +local:root (asking you to install x11-xserver-utils if xhost is missing), then checks
that /dev/video0 and /dev/dri exist. xhost grants reset at logout — run it once per login.
privileged: true — and what it really means
#
privileged hands the containers every host device and almost every kernel capability. It’s there for
convenience: hot-plugged cameras and GPU nodes just work. Combined with host network, IPC and PID, and
xhost +local:root, these containers are effectively not sandboxed from the host. Fine on a trusted
workstation running your own code; not fine anywhere else.
Hardening, in order of effort:
- Drop
privileged: true— the explicitdevices:already grant camera and GPU; addgroup_add: ["video", "render"]if permissions complain. - Replace
xhost +local:rootwith a per-container Xauthority cookie, orxhost +SI:localuser:$(id -un)and run as your user. - Run as non-root (
user: "${UID}:${GID}") with the same UID in both services, so Fast DDS shared memory keeps working. - Narrow the
ipc/pidsharing (Part 17).