webstation-broker
Container

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-stopped

docker 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:romm

The 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

ParameterFunction
-p 3000Plain, 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 /configThe 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/libraryYour 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=1gbSets the container's shared memory size. Recommended for all desktop images like this one.
-e PUID / -e PGIDThe 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 SUBFOLDERThe 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_SECRETA 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_MODESet 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.

webstation docker-compose.yml
    devices:
      - /dev/dri:/dev/dri
webstation docker cli
  --device /dev/dri \

If your host has more than one GPU, pin (select) the node the container should use, again in the webstation service:

webstation docker-compose.yml
    environment:
      - DRINODE=/dev/dri/renderD129
      - DRI_NODE=/dev/dri/renderD129

xemu 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: WEBSTATION

host 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/library

RomM 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.

On this page