Files
2026-08-05 18:31:21 +02:00
..
2026-08-05 18:31:21 +02:00
2026-08-01 22:43:09 +02:00

Server

The authoritative server executable.

Running

Usage: tw_server [--quicr-port <port>] [--cluster-port <port>]
  --quicr-port    UDP port for player connections  (default: 8101)
  --cluster-port  UDP port for zone-server peering (default: 8102)

Metrics reporting to TimescaleDB stays off until TIMESCALEDB_HOST is set:

Variable Default
TIMESCALEDB_HOST unset (off)
TIMESCALEDB_PORT 5432
TIMESCALEDB_DB mmo
TIMESCALEDB_USER mmo
TIMESCALEDB_PASSWORD empty
TIMESCALEDB_TABLE zone_metrics

Building natively

See the root README.md. The target is tw_server and the binary lands in <build-dir>/modules/server/tw_server.

Building the image

Dockerfile has three stages: a toolchain stage holding the C++ build environment, a build stage that compiles tw_server with clang, and a runtime stage that carries only the binary and its shared libraries. The build context is the repository root, so run it from there:

$ docker build -f modules/server/Dockerfile -t tw_server .

The toolchain stage installs the compiler and tools (clang, cmake, ninja, mold, glslangValidator) and the development packages CMake looks for (Vulkan, SDL2, protobuf, libpq, libpqxx) from apt. It touches no source, so it only rebuilds when that package list changes.

The build stage configures a Release build on top of it with CC=clang / CXX=clang++. glm, entt, Jolt, spdlog, expected, Catch2 and tracy are cloned while configuring, so the build needs network access. .dockerignore keeps the local build directories and those cloned sources out of the context.

Reusing the toolchain

Layer caching already keeps apt out of a rebuild, but the cache is local and dies with docker builder prune. To pin the toolchain down, build that stage on its own and tag it:

$ docker build -f modules/server/Dockerfile --target toolchain -t tw_toolchain .

TOOLCHAIN_IMAGE then points the build stage at it, and the apt step is skipped outright rather than cache-hit:

$ docker build -f modules/server/Dockerfile \
      --build-arg TOOLCHAIN_IMAGE=tw_toolchain -t tw_server .

The default is the in-file toolchain stage, so a plain build still works standalone. Any registry tag works too, which is the useful form on CI. To carry it between machines by hand:

$ docker save tw_toolchain | zstd -o tw_toolchain.tar.zst
$ zstd -dc tw_toolchain.tar.zst | docker load

The runtime stage is gcr.io/distroless/cc-debian13:nonroot — glibc, libstdc++ and a nonroot user, no shell and no package manager. Since nothing can be installed there, the build stage walks ldd over the binary and stages every shared library it resolved into /rootfs, which the runtime stage copies in whole. glibc and the loader are filtered out of that list: the binary is built against 2.39 and the runtime image ships 2.41, which runs it, but the two must not be mixed.

Nothing in the image can be executed except the server, so docker exec and docker run --entrypoint are of no use for poking around. Swap the base for gcr.io/distroless/cc-debian13:debug-nonroot when a busybox shell is needed.

Run it, publishing both UDP ports:

$ docker run --rm -p 8101:8101/udp -p 8102:8102/udp tw_server

Arguments after the image name reach the binary, and environment variables are passed as usual:

$ docker run --rm -p 9101:9101/udp -e TIMESCALEDB_HOST=timescale \
      tw_server --quicr-port 9101