Skip to content

Self-hosting a Forge room

A self-hosted node is a headless room host. It connects to a relay server, opens a lobby room, and runs the games for everyone who joins — players only need the web or desktop client. With the forge backend the node spawns one Forge process per game, so games are played on the original Forge rules engine rather than the Rust port.

The node authenticates to the relay with the relay’s server key. Set it via SELF_HOSTED_NODE_SERVER_KEY — never commit it anywhere.

The image is published on GHCR and bundles everything: the node binary, the Forge harness, and a JRE — no checkout needed. Create a compose.yml:

services:
forge-room:
image: ghcr.io/witchesofthehill/manabrew-node:latest
environment:
SELF_HOSTED_NODE_RELAY_URL: "wss://relay.manabrew.app"
SELF_HOSTED_NODE_SERVER_KEY: "${SELF_HOSTED_NODE_SERVER_KEY:?required}"
SELF_HOSTED_NODE_ROOM_NAME: "my-room"
RUST_LOG: "self_hosted_node=info"
restart: unless-stopped

The image defaults to the Forge backend, so no extra configuration is needed:

Terminal window
SELF_HOSTED_NODE_SERVER_KEY= docker compose up -d

To build the image yourself instead of pulling, replace the image: line with a build: block (needs a full checkout — git clone --recurse-submodules https://github.com/witchesofthehill/manabrew.git):

build:
context: .
dockerfile: manabrew-rs/crates/self-hosted-node/Dockerfile

One node can host any number of rooms — set SELF_HOSTED_NODE_MAX_GAMES and it opens that many lobbies, suffixed my-room (1), my-room (2), …. Each game still runs in its own Forge subprocess, so rooms are fully isolated from each other:

services:
forge-rooms:
image: ghcr.io/witchesofthehill/manabrew-node:latest
environment:
SELF_HOSTED_NODE_RELAY_URL: "wss://relay.manabrew.app"
SELF_HOSTED_NODE_SERVER_KEY: "${SELF_HOSTED_NODE_SERVER_KEY:?required}"
SELF_HOSTED_NODE_ROOM_NAME: "my-room"
SELF_HOSTED_NODE_MAX_GAMES: "4"
RUST_LOG: "self_hosted_node=info"
restart: unless-stopped

In multi-room mode the format is forced to any — players pick per game. Memory scales with games in progress (one JVM each), not with idle lobbies.

To scale across containers instead (e.g. to spread rooms over hosts or set per-room memory limits), run several replicas of the same service:

Terminal window
SELF_HOSTED_NODE_SERVER_KEY= docker compose up -d --scale forge-rooms=3

Each replica is an independent node with its own rooms, so total rooms = replicas × SELF_HOSTED_NODE_MAX_GAMES. Replicas share the same SELF_HOSTED_NODE_ROOM_NAME; give each its own service entry instead if you want distinct lobby names.

The forge backend above spawns one Forge process per game (it ships a JRE and the harness jar). As an alternative, Forge can be compiled ahead-of-time with GraalVM native-image into a shared library that the node links and calls in-process — no JVM and no jar subprocess.

Build the library (needs GraalVM for JDK 21 — see the native build README), then run the node with the graal-forge feature instead of java-forge:

Terminal window
yarn build:harness
./forge-harness/build-native.sh
SELF_HOSTED_NODE_ROOM_NAME=my-room \
SELF_HOSTED_NODE_SERVER_KEY=<your-server-key> \
SELF_HOSTED_NODE_ENGINE_BACKEND=forge \
SELF_HOSTED_NODE_RELAY_URL=wss://relay.manabrew.app \
cargo run --release -p self-hosted-node --features graal-forge

native-image cannot cross-compile, so build the library on each platform you intend to run the node on.

All settings are environment variables:

VariableDefaultPurpose
SELF_HOSTED_NODE_RELAY_URLws://127.0.0.1:9443Relay server to connect to
SELF_HOSTED_NODE_SERVER_KEYRelay server key (must match the relay’s)
SELF_HOSTED_NODE_ROOM_NAMESelf-Hosted NodeLobby name shown to players
SELF_HOSTED_NODE_ROOM_PASSWORDnoneRequire a password to join
SELF_HOSTED_NODE_FORMATanyGame format (e.g. commander)
SELF_HOSTED_NODE_MAX_PLAYERS4Seats in the room
SELF_HOSTED_NODE_MAX_GAMES1Rooms the node hosts (one concurrent game each)
SELF_HOSTED_NODE_ENGINE_BACKENDmanabrewforge for the Forge engine, manabrew for the ManaBrew engine
SELF_HOSTED_NODE_BOT_ENABLEDfalseSeat an AI bot in the room
SELF_HOSTED_NODE_AUTO_STARTfalseStart as soon as the room fills