Running the container
The linuxserver/webstation:romm image that ships the broker, how to start it, and what it needs from the host.
You don't run the broker by itself. It ships inside
linuxserver/docker-webstation,
on the romm branch, as the image lscr.io/linuxserver/webstation:romm. That
image is a full Linux desktop, streamed to your browser with software called
selkies, with every supported emulator already installed and nginx (a web
server) in front. The broker is registered as one of the container's
background services, managed by an init system called s6, and it starts once
selkies's control plane (the part of selkies it talks to) is ready.
Setting this up for the first time? The Quickstart walks through this page in order, alongside the rest of the setup.
The image only works in Wayland mode (the graphics system newer Linux desktops use) and is meant to run with a GPU. Without a GPU, the desktop itself still works using software rendering, but most emulators will not.
docker-compose
---
services:
webstation:
image: lscr.io/linuxserver/webstation:romm
container_name: webstation
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- SUBFOLDER=/streaming/
- BROKER_SECRET=change-me
volumes:
- /path/to/config:/config
- /path/to/library:/romm/library
# optional: pre-seed RetroArch BIOS/firmware instead of drag-and-drop
# - /path/to/bios:/config/.config/retroarch/system
ports:
- 3000:3000
shm_size: "1gb"
restart: unless-stoppeddocker cli
docker run -d \
--name=webstation \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Etc/UTC \
-e SUBFOLDER=/streaming/ \
-e BROKER_SECRET=change-me \
-p 3000:3000 \
-v /path/to/config:/config \
-v /path/to/library:/romm/library \
-v /path/to/bios:/config/.config/retroarch/system \
--shm-size="1gb" \
--restart unless-stopped \
lscr.io/linuxserver/webstation:rommThe BIOS mount is optional. It only applies to RetroArch's shared system directory, where RetroArch looks for BIOS and firmware files. Skip it (and remove the commented-out line in the compose example above) if you'd rather upload those files through the desktop instead. See Getting files into the container for the matching path to mount for other emulators.
Parameters
| Parameter | Function |
|---|---|
-p 3000 | Plain, unencrypted web traffic (HTTP). This is the only port you need to publish, and it must sit behind a reverse proxy, which provides the HTTPS. See Reverse proxy. |
-v /config | The user's home directory inside the container. Holds emulator settings, BIOS and firmware, and save data. It's also where the broker keeps its pid record (a small file it uses to find the running emulator again after a restart) and its export directory. |
-v /romm/library | Your ROM library, which you can mount read-only if you like. Use the same host folder and the same /romm/library path as the library mount on your RomM container, so the ROM paths RomM sends are valid here without any extra config. See Mounting the library somewhere else if you can't. |
-v .../retroarch/system (optional) | A host folder already filled with BIOS and firmware, mounted at RetroArch's shared system directory (RETROARCH_SYSTEM_DIR, default /config/.config/retroarch/system) so you don't have to drag files into the desktop by hand afterward. See Getting files into the container. |
--shm-size=1gb | Sets the container's shared memory size. Recommended for all desktop images like this one. |
-e PUID / -e PGID | The user ID and group ID (uid/gid) that the desktop, the emulators, and the broker all run as. Match them to the owner of your mounted directories so file permissions line up. |
-e SUBFOLDER | The URL path the whole app is served under, default /streaming/. Must exactly match the path your reverse proxy mounts the container at, trailing slash included. |
-e BROKER_SECRET | A shared password that RomM sends as the X-Broker-Secret header on the lifecycle endpoints (the API calls that start and stop sessions), so the broker knows the request really came from RomM. Unset, the broker refuses to start unless BROKER_DEV_MODE is also set, in which case it starts with those endpoints unauthenticated. |
-e BROKER_DEV_MODE | Set to true to run the broker from source code mounted at /broker. See Dev mode. |
Every other broker and emulator variable is optional. They're listed under Configuration.
GPU
Mount your GPU into the container and the base image sets it up automatically, for both rendering the desktop and encoding the video stream. No extra variables are needed. The full guide, covering multi-GPU pinning (choosing which GPU to use when you have more than one), hardware encoders, and troubleshooting, is the selkies GPU acceleration page. Here's the minimum you need to add:
Add these to the webstation compose, not RomM's
Everything below goes under the webstation service in the compose file you
just wrote for this container (the one running
lscr.io/linuxserver/webstation:romm). Don't add any of it to RomM's own
compose file: RomM doesn't render or encode anything, so giving it the GPU
does nothing. The snippets are indented to sit directly inside the
webstation: service, next to image: and volumes:. The docker cli
flags go on the docker run command for the webstation container.
Open-source drivers only need the render node (a device file for the GPU) passed through.
devices:
- /dev/dri:/dev/dri --device /dev/dri \If your host has more than one GPU, pin (select) the node the container
should use, again in the webstation service:
environment:
- DRINODE=/dev/dri/renderD129
- DRI_NODE=/dev/dri/renderD129xemu is pinned to a renderer (XEMU_RENDERER) known to work on the
integrated AMD graphics this image is tested on. Change it if your driver
prefers Vulkan instead. PCSX2 has no such
pin: its renderer setting lives in PCSX2.ini, picked by its own setup
wizard. So a "Failed to initialize GS" startup error on an AMD integrated
GPU means RADV (AMD's Vulkan driver) failed to start on that hardware.
Switch PCSX2's renderer to OpenGL instead, in Settings > Graphics: it
uses a different driver (radeonsi) that's confirmed to work.
First run
The container boots straight to the webstation desktop. Before you launch
games through RomM, every emulator you plan to use needs its BIOS, firmware,
or decryption keys set up, plus a quick controller check. Launch a desktop
session from RomM (or open /streaming/ on your RomM address directly) and
follow Emulator setup.
Wiring it into RomM
RomM talks to the container over REST (a common way for apps to communicate
over plain HTTP) and embeds its room in an iframe (a webpage shown inside
another webpage). Add this to RomM's config.yml:
streaming:
containers:
- platform: ps2
host: /streaming
broker_host: http://10.0.1.56:3000
subfolder: /streaming
broker_secret: "change-me"
protocol: webstation
emulator: pcsx2
label: WEBSTATIONhost is the address your browser connects to. broker_host is the address
RomM's backend server uses to reach the broker directly, server to server,
not through your browser. If RomM and the container share a Docker network,
it can be the service name and port 3000 (http://romm_webstation:3000)
instead of an IP. subfolder should match the container's
SUBFOLDER. The full story, including why host is written as a path, is in
Reverse proxy.
There is no path setting here. RomM sends each ROM as a path under its own
library folder (/romm/library by default), and because this container
mounts the same folder at the same path, that path already works here.
Mounting the library somewhere else
If this container can't mount the library at /romm/library (a broker on
another host with the library on a network share, say), mount it wherever it
has to go and add library_path to the container entry in RomM's
config.yml, naming the path this container sees:
library_path: /mnt/games/libraryRomM then swaps its own library prefix for that one. If the new path isn't
under /romm, also set the container's ROM_ROOT to it or a folder above it,
since activate refuses any ROM path outside ROM_ROOT. See
Broker settings.