Battlezone Online is a real-time multiplayer tank game built with Node.js, WebSockets, and Three.js.
There are two supported ways to run the game:
- Docker image from GitHub Container Registry
- Source release tarball or git checkout
For most users, Docker is the best install and update path.
Each tagged release publishes:
- a GitHub release with notes generated from CHANGELOG.md
- a source tarball
- a versioned Ubuntu 26.04 image at
ghcr.io/timriker/bzo:<version>-ubuntu26.04 - a moving
ubuntu26.04tag ghcr.io/timriker/bzo:<version>andghcr.io/timriker/bzo:latest, both using Ubuntu 26.04
Every published image contains linux/amd64 and linux/arm64 variants. Release
tags use stable vX.Y.Z SemVer only; prerelease and build-metadata tags are not
published. Ubuntu 26.04 images use the pinned Node.js 24.19.0 runtime.
Docker images are built on Ubuntu 26.04 with pinned Node.js 24.19.0.
Runtime compatibility is validated in CI on Node.js 18.19.1 and 24.19.0.
Use compose.yml:
docker compose up -dThis starts the server on port 3000 and stores runtime config in ./data/server.json.
On first start, the server copies example-server.json to the configured runtime path if no config exists.
Naming update: this project now uses compose.yml, server.json, and
example-server.json only.
Then open:
http://localhost:3000
The image is multi-arch (linux/amd64 and linux/arm64), so Docker will pull the
correct variant for your host by default.
If you need to force an architecture, set platform in compose:
services:
bzo:
image: ghcr.io/timriker/bzo:latest
platform: linux/amd64 # or linux/arm64
volumes:
- ./data:/datadocker run -d \
--name bzo \
-p 3000:3000 \
-v bzo-data:/data \
ghcr.io/timriker/bzo:latestThe image defaults to SERVER_CONFIG_PATH=/data/server.json.
To force a specific architecture when running directly:
docker run -d \
--name bzo \
--platform linux/amd64 \
-p 3000:3000 \
-v bzo-data:/data \
ghcr.io/timriker/bzo:latestUse --platform linux/arm64 on ARM hosts if you want to pin that explicitly.
- Persist server settings and runtime config by mounting
/data(already done incompose.yml). SERVER_CONFIG_PATHdefaults to/data/server.json.- The container runs as UID/GID
1000:1000; for bind mounts, ensure the host./datadirectory is writable by that user (for examplechown -R 1000:1000 ./data).
Built-in maps ship inside the image at /app/maps.
Runtime map uploads and operator-managed custom maps are stored in a writable
runtime maps directory that defaults to $(dirname $SERVER_CONFIG_PATH)/maps.
With the default Docker settings, this is /data/maps, which is already
persisted by the existing ./data:/data volume.
No extra volume is required for operator uploads to persist across restarts.
If you want to override the runtime map directory, set MAPS_PATH:
services:
bzo:
image: ghcr.io/timriker/bzo:latest
environment:
SERVER_CONFIG_PATH: /data/server.json
MAPS_PATH: /data/maps
volumes:
- ./data:/dataYou can still provide static read-only maps in the image path, but uploaded maps should go to the runtime directory.
- Node.js 18.19.1 or Node.js 24.19.0
- npm
npm installIf server.json does not exist, the server will create it from example-server.json on first start.
Production:
npm startDevelopment:
npm run devThen open:
http://localhost:3000
Runtime configuration lives in server.json by default.
You can override the path with:
SERVER_CONFIG_PATH=/path/to/server.json npm startSee example-server.json for the supported shape.
There is no built-in self-update path for source installs.
To update, download a newer release or pull newer source, then run:
npm installDocker is the recommended update path.
Manual update:
docker compose pull
docker compose up -dor:
docker pull ghcr.io/timriker/bzo:latestIf you want automatic container updates, use your preferred container update manager. That is not built into the game itself.
- Human-readable history is kept in CHANGELOG.md
- Tagged GitHub releases use the matching changelog section as release notes
W/SorUp/Down— move forward/backwardA/DorLeft/Right— turn left/rightSpace— shootTab— jumpQ— self-destructP— pause/resumeN— open chatEnter— send chat (while chat input is focused)Esc— exit chat input or mouse modeM— toggle mouse movementC— cycle camera modeO— toggle operator panelF— toggle fullscreenI— toggle debug HUDB— toggle nearby voice microphone/or?— show/hide help panel
The VR mode uses native WebXR and requires a browser and headset that support
immersive-vr. For local validation, open the game at http://localhost:3000.
For remote access, terminate TLS at the reverse proxy and open the game over
https://; the client automatically uses wss:// for its WebSocket connection
when the page is served over HTTPS.
If the deployment sets a restrictive Permissions-Policy header, allow
xr-spatial-tracking=(self). The Node.js server does not terminate TLS itself,
so HTTPS and the corresponding WebSocket proxy configuration are deployment
responsibilities.
Use the WebXR validation checklist when checking a new browser, headset, or deployment. WebGPU rendering is outside the scope of this checklist.
npm run checkThis runs syntax and lint checks.
CI also runs these checks on pushes and pull requests.
Prepare a release locally:
npm run release:prepare -- 1.0.1That updates:
package.jsonpackage-lock.jsonCHANGELOG.md
Then edit the new changelog section so it contains the real user-visible changes.
Validate locally:
npm run check
npm run release:check -- v1.0.1
npm run release:check:increment -- v1.0.1Then commit, tag, and push:
git add package.json package-lock.json CHANGELOG.md
git commit -m "Release v1.0.1"
git tag v1.0.1
git push
git push origin v1.0.1The release workflow will:
- verify that the stable tag is newer than the previous release and points to
main - install dependencies and run lint, validation, audit, and CodeQL checks
- fail if
package.jsondoes not match the pushed tag - fail if CHANGELOG.md does not contain a matching non-placeholder section
- build and smoke-test Ubuntu 26.04 images with pinned Node.js
24.19.0forlinux/amd64andlinux/arm64 - promote the verified versioned and moving Docker tags to GHCR
- publish a GitHub release and attach a source tarball
This project is licensed under the GNU Affero General Public License v3.0.
Network users can access the source code from the running app via /source, or directly at: