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.
Running the node
Section titled “Running the node”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-stoppedThe image defaults to the Forge backend, so no extra configuration is needed:
SELF_HOSTED_NODE_SERVER_KEY=… docker compose up -dTo 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/DockerfileYou need a Rust toolchain, a JDK (17+), Maven, and a full checkout:
git clone --recurse-submodules https://github.com/witchesofthehill/manabrew.gitcd manabrewBuild the Forge harness jar and the cardset archive once:
mvn -pl forge-harness -am package -DskipTestscargo run --release -p forge-cardset-archive --features build --bin build-cardset-archiveThen run the node:
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 \JAVA_HOME="$(/usr/libexec/java_home)" \cargo run --release -p self-hosted-node --features java-forge$(/usr/libexec/java_home) resolves the JDK path on macOS; on Linux point
JAVA_HOME at your JDK installation (for example
/usr/lib/jvm/temurin-21-jdk).
Hosting multiple rooms
Section titled “Hosting multiple rooms”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-stoppedIn 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:
SELF_HOSTED_NODE_SERVER_KEY=… docker compose up -d --scale forge-rooms=3Each 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.
Native Forge engine (GraalVM)
Section titled “Native Forge engine (GraalVM)”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:
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-forgenative-image cannot cross-compile, so build the library on each platform you
intend to run the node on.
Configuration
Section titled “Configuration”All settings are environment variables:
| Variable | Default | Purpose |
|---|---|---|
SELF_HOSTED_NODE_RELAY_URL | ws://127.0.0.1:9443 | Relay server to connect to |
SELF_HOSTED_NODE_SERVER_KEY | — | Relay server key (must match the relay’s) |
SELF_HOSTED_NODE_ROOM_NAME | Self-Hosted Node | Lobby name shown to players |
SELF_HOSTED_NODE_ROOM_PASSWORD | none | Require a password to join |
SELF_HOSTED_NODE_FORMAT | any | Game format (e.g. commander) |
SELF_HOSTED_NODE_MAX_PLAYERS | 4 | Seats in the room |
SELF_HOSTED_NODE_MAX_GAMES | 1 | Rooms the node hosts (one concurrent game each) |
SELF_HOSTED_NODE_ENGINE_BACKEND | manabrew | forge for the Forge engine, manabrew for the ManaBrew engine |
SELF_HOSTED_NODE_BOT_ENABLED | false | Seat an AI bot in the room |
SELF_HOSTED_NODE_AUTO_START | false | Start as soon as the room fills |