Baseimage Internals¶
What actually happens inside a Selkies container, boot order, services, and the files involved. Reference material for image builders and anyone debugging a container from the inside.
Build composition¶
The baseimage Dockerfile is a multi stage build assembling, onto a LinuxServer.io distro base (Debian, Ubuntu, Alpine, Fedora, Arch, or Kali):
- An XLibre Xvfb (
xlibre-xserver-25.2.2) built with glamor and DRI3 from selkies-layers with four patches: the screen pixmap lives on the GPU (-glamor -dri /dev/dri/renderD###) and the framebuffer follows RandR resizes; the server starts with four CRTCs whose spare outputs stay disconnected until Selkies plugs a second display in; DRI3 presents synchronize through timeline syncobjs where the driver supports them; and the fake vblank follows the rate pixelflux publishes on the root window - The web frontend: the Selkies repository pinned to the release, with
selkies-web-coreand the dashboards built and dropped in/usr/share/selkies/ - labwc 0.9.7 built from source in selkies-layers with two patches: the IPC socket (
labwc -i, a read only JSON window query socket at$XDG_RUNTIME_DIR/labwc.sock, consumed by Pelorus) and a control socket Selkies grows and removes a second screen over. On distros without a packaged wlroots 0.19 the layer carries wlroots 0.19.3 too - selkies-desktop and waylandtyper (our maintained fork of wtype) from their selkies-layers builds; the KDE Webtops additionally take a patched KWin from the same place for their second display
- The selkies Python package installed into the
/lsiopyvirtualenv (pulling in pixelflux and pcmflux wheels), plus pelorus - The input interposer (
/usr/lib/selkies_input_interposer.so) and fake udev (/opt/lib/libudev.so.1.0.0-fake) compiled from the Selkies addons - Nginx, PulseAudio, CUPS (
cups-daemonandcups-filtersfor the session's print queue), mesa and VA-API userspace, Vulkan loaders, all system locales, proot-apps, Docker in Docker machinery, and passwordless sudo forabc
Baked ENV defaults worth knowing: HOME=/config, DISPLAY=:1, TITLE=Selkies, SELKIES_ENCODER="h264enc,h265enc,vp8enc,vp9enc,av1enc,jpeg", SELKIES_RATE_CONTROL_MODE="crf,cbr", SELKIES_ENABLE_BASIC_AUTH=false, SELKIES_VIDEO_STREAMING_MODE=false, SELKIES_ALLOWED_ORIGINS="*", START_DOCKER=true, DISABLE_DRI3=false, NVIDIA_DRIVER_CAPABILITIES=all, __GL_SYNC_TO_VBLANK=0, and the interposer paths in SELKIES_INTERPOSER and SELKIES_WEBCAM_INTERPOSER.
Boot: the init chain¶
The Selkies oneshots hook into the standard LSIO s6 chain right after init-os-end (so PUID and PGID remapping, Docker mods, and custom files all run in their usual order around them):
init-os-end
└─ init-selkies (marker)
└─ init-nginx
└─ init-selkies-config
└─ init-video
└─ init-selkies-end → init-config → ... → init-services
init-nginx renders /defaults/default.conf into the live Nginx config with plain string substitution: ports (CUSTOM_PORT, CUSTOM_HTTPS_PORT, CUSTOM_WS_PORT), SUBFOLDER tokens, basic auth (generates .htpasswd and uncomments the auth lines when PASSWORD is set), IPv6 removal, dev mode rewiring, dashboard selection into /usr/share/selkies/web, and the PWA manifest.json with your TITLE. It creates FILE_MANAGER_PATH and, on first run, generates the ten year self signed certificate into /config/ssl/.
init-selkies-config is the big one:
- Chooses the mode:
PIXELFLUX_WAYLAND=trueselects labwc paths ($HOME/.config/labwc,/defaults/autostart_wayland,/defaults/menu_wayland.xml) otherwise Openbox paths. Multi monitor on Wayland is Selkies' call, not the init script's: at startup it probes the compositor, the labwc IPC socket or a KWin capability check, and enables the second screen only where it finds support. Both compositors carry LinuxServer patches for this, the labwc ones ship in the baseimage. - First run copies of
autostartandmenu.xmlinto the config dir (persistent, user editable);rc.xmlfor labwc is regenerated from the template every boot. - Recreates
$HOME/.XDGasXDG_RUNTIME_DIR, which is where the interposer sockets and the print queue's socket live, pointsCUPS_SERVERat that queue unless you set it or turned printing off, and clears stale PulseAudio state, so unclean shutdowns recover. - Applies every hardening variable (permission stripping, sudoers corruption, menu and keybind editing, locking rc.xml and autostart when watchdog mode is on), the details are in the Security guide.
- GPU env: with exactly one render node and nothing set, points
DRINODEandDRI_NODEat it; wiresPIXELFLUX_CU=5000, a generatedPIXELFLUX_CU_TOKENunless you set one, andROOT_PATH=/peloruswhenPELORUS=true. - Creates the gamepad device nodes (
/dev/input/js0-3and event nodes) and sets the globalLD_PRELOADfor the interposer and fake udev, unlessNO_GAMEPADis set. - Syncs proot-apps into the user home and handles
LC_ALLlocale derivation. - Steam shim: links
/usr/local/bin/steam(marked#SELKIESSHIM) to/usr/bin/steamwhen nothing is there, sosteamruns the installer in/steam.shuntil a real launcher exists.NO_STEAM=trueremoves both.
init-video fixes /dev/dri and /dev/dvb group permissions for abc (creating a matching group for the device GID when needed), auto enables AUTO_GPU on x86_64 when a render node exists and nothing was configured, probes whether older Intel hardware needs the i965 VA-API driver, and repairs Nvidia ICD, Vulkan, EGL, and GBM plumbing inside the container.
The services¶
| Service | Behavior |
|---|---|
svc-nginx | Reaps zombie workers then runs Nginx in the foreground |
svc-pulseaudio | PulseAudio as abc, never idle exits, runtime dir under /defaults |
svc-xorg | X11 mode: picks the render node (DRINODE, else the Nvidia node when one exists, else the first) and starts Xvfb on :1 with -glamor -dri <node> so the screen lives on the GPU, unless DISABLE_DRI3=true or no node exists. Wayland mode: sleeps |
svc-selkies | Loads the output and input null sinks once PulseAudio is up, then runs selkies --addr=localhost as abc, every other setting coming from the environment. In Wayland mode this is what brings up the pixelflux compositor on wayland-1 |
svc-de | Waits for the display (the wayland-1 socket, or xset q on X11), then executes /defaults/startwm_wayland.sh or /defaults/startwm.sh as abc, recording the PID for clean teardown. On X11 it also sets the initial 1024x768 mode (or SELKIES_MANUAL_WIDTH and HEIGHT) via xrandr |
svc-xsettingsd | X11 only, DPI hinting for legacy toolkits |
svc-watchdog | Only active with RESTART_APP=true: polls for the autostart process and relaunches it if it dies |
svc-dbus | System D-Bus (present on distros that need it) |
svc-docker | Detects privileged mode and starts the inner Docker daemon unless START_DOCKER=false |
Teardown is equally deliberate: svc-de's finish script walks the session's process tree with a graceful TERM and a five second deadline, so recreating containers does not leave half dead sessions.
Nginx layout¶
One config, two identical server blocks (HTTP 3000, HTTPS 3001):
| Location | Purpose |
|---|---|
SUBFOLDER (default /) | The web client static files from /usr/share/selkies/web/ |
SUBFOLDERapi | Proxy to the Selkies server on 127.0.0.1:8082 with the prefix kept, which Selkies also reads from SUBFOLDER: the data WebSocket, WebRTC signaling, the file browser and uploads, printed documents, the operator API, and the token control plane. Unlimited body size and no request buffering, so uploads stream through |
SUBFOLDERpelorus/ | Proxy to the Pelorus API on 127.0.0.1:5100 |
/devmode | Proxy to a Vite dev server, see Development Environment |
All proxy locations use hour long timeouts and no response buffering. Because substitution is plain sed, exotic characters in PASSWORD or SUBFOLDER can break the config, keep them simple.
Internal port map¶
| Port | Owner | Exposure |
|---|---|---|
| 3000, 3001 | Nginx | Published, everything user facing |
| 8082 | Selkies server, everything under /api including the token control plane | localhost only, via Nginx |
| 5100 | Pelorus API | localhost only, via Nginx at /pelorus/ |
| 5000 | pixelflux Computer Use API | localhost only |
| 5173 | Vite dev server | localhost only, via /devmode |
Wayland session wiring¶
/defaults/startwm_wayland.sh in the baseimage handles four combinations of PELORUS and SELKIES_DESKTOP: plain labwc; labwc plus the selkies-desktop panel; labwc with IPC plus the Pelorus and AT-SPI stack; or all of it together, in each case exporting the cursor theme, us XKB defaults, and WAYLAND_DISPLAY=wayland-1 for labwc while the shell and apps land on wayland-0 with DISPLAY=:0 for XWayland. Downstream desktops override this file wholesale, see Building Custom Images.
Things inherited from the LSIO base¶
Not implemented here, but always available: PUID, PGID, TZ, UMASK, DOCKER_MODS, /custom-cont-init.d, and /custom-services.d. Those hooks are the subject of Customizing Containers.