AOS REVIVALMenuJoin Discord
Server guide 03

Run BattleSpades in Docker

The official container image is the easiest way to run BattleSpades on a Linux VPS. Configuration comes from a few environment variables, all writable data lives in one volume, and the process drops to an unprivileged user before any game code runs.

Checked BattleSpades 0.1.0-beta.110 minute read

The image

PropertyValue
Registryghcr.io/kikots/battlespades
Tagsmain (latest successful build of main), sha-<commit> (immutable, use for production), and v… release tags
Platformlinux/amd64
Built by.github/workflows/container.yml — only after the complete test suite passes; images carry provenance and an SBOM
Basepython:3.12-slim-bookworm, compiled in a separate build stage
Exposed port27015/udp
UserUID/GID 10001 (battlespades); only /data is writable
Entrypointtini → scripts/container_init.sh → scripts/container_entrypoint.py

On start the entrypoint copies the image's config.toml, applies the environment variables below, writes the effective file to /data/runtime/config.toml, and starts the normal server. Logs go to /data/logs/ and bans to /data/bans.json. The Windows-only Steam browser helper is always disabled inside the container.

Quick start

Run one serverTXT
docker volume create battlespades-data

docker run -d --name battlespades --restart unless-stopped \
  -p 27015:27015/udp \
  -e BATTLESPADES_ADMIN_PASSWORD='replace-with-a-long-secret' \
  -e BATTLESPADES_SERVER_NAME='My AoS Server' \
  -e BATTLESPADES_MODE=ctf \
  -e BATTLESPADES_MAP=CastleWars \
  -e BATTLESPADES_REVIVAL_ENABLED=false \
  -v battlespades-data:/data \
  ghcr.io/kikots/battlespades:main

docker logs -f battlespades
  • The admin password is required (12+ characters); the container refuses to start with the sample changeme.
  • Publish the same UDP port inside and outside (-p 27015:27015/udp). To use another port, set BATTLESPADES_PORT=27025 and publish -p 27025:27025/udp.
  • BATTLESPADES_REVIVAL_ENABLED=false keeps a test container off the AoSPlay list. For a public server, see the listing variables below.
  • Validate without starting: docker run --rm ghcr.io/kikots/battlespades:main --check.
Probe it from the host (source checkout)TXT
python deploy/a2s_probe.py --host 127.0.0.1 --port 27015

Environment variables

The image deliberately exposes a small, validated set of overrides. Values are checked (ranges, safe characters, map names never paths) and a bad value stops the container with a BattleSpades container configuration error message. Every other key comes from the config template.

VariableApplies toEffect
BATTLESPADES_SERVER_NAMEDocker imageSets [server] name (up to 64 characters accepted; clients show 31).
BATTLESPADES_PORTDocker imageSets [server] port, the UDP game and A2S port (1–65535).
BATTLESPADES_MAX_PLAYERSDocker imageSets [server] max_players (1–255).
BATTLESPADES_MODEDocker imageSets [game] default_mode (a mode code or alias).
BATTLESPADES_MAPDocker imageSets [game] default_map. A map name, never a path.
BATTLESPADES_BOT_COUNTDocker imageFixed bot population (0–254): sets [bots] to fixed with this many bots, or disables bots at 0.
BATTLESPADES_REGIONDocker imageSets [revival] region.
BATTLESPADES_OFFICIALDocker imageSets [revival] official (true/false, yes/no, on/off, 1/0).
BATTLESPADES_REQUIRE_IDENTITYDocker imageSets [revival] require_identity.
BATTLESPADES_REVIVAL_ENABLEDDocker imageSets [revival] enabled. Use false for a private test container.
BATTLESPADES_ADMIN_PASSWORDDocker imageSets [admin] password; must be at least 12 characters. The container refuses to start while the password is still changeme.
BATTLESPADES_ALLOW_INSECURE_DEFAULTSDocker imageSet to true only to start a throwaway container with the sample changeme password.
BATTLESPADES_DATA_DIRDocker imageWritable data directory, /data or below (default /data). Holds the generated config, logs and bans.json.
BATTLESPADES_CONFIG_TEMPLATEDocker imageTOML file used as the template (default: the image's config.toml). Mount your own complete config here to change keys the other variables do not cover.
BATTLESPADES_RUNTIME_CONFIGDocker imageWhere the effective config is written (default /data/runtime/config.toml).
AOS_MASTER_WRITE_TOKENany launchThe server-scoped aos_srv_… token AoSPlay returns when you register. Required for listing, identity tickets and results; never put it in config.toml.
AOS_MASTER_URLany launchOverrides [revival] base_url.
AOS_PUBLIC_HOSTany launchOverrides [revival] public_host: the public IPv4 players connect to.
AOS_PUBLIC_PORTany launchPublic UDP game port when it differs from the listen port (router mapping or a relay such as Playit).
AOS_PUBLIC_QUERY_PORTany launchPublic A2S port. Defaults to the public game port when AOS_PUBLIC_PORT is set.
AOS_SERVER_IDany launchOverrides [revival] server_id; must equal public_host:public_port.
BATTLESPADES_STEAM_RUNTIMEany launchFallback for [steam] runtime_dir.
BATTLESPADES_STEAMCLIENT_RUNTIMEany launchFallback for [steam] steamclient_dir.
BATTLESPADES_STEAM_HELPERany launchFallback for [steam] helper_path.

Make a container public

  1. Open UDP

    Allow the published UDP port in the VPS provider's firewall and the host firewall.

  2. First boot without a token

    Start with AOS_PUBLIC_HOST set to the node's public IPv4 and no AOS_MASTER_WRITE_TOKEN.

  3. Register on the node

    Run deploy/register_server.py on the game node once the A2S probe passes (see Ports and public listing).

  4. Recreate with the token

    Put the printed aos_srv_… token in AOS_MASTER_WRITE_TOKEN and recreate only that container.

Extra variables for a listed serverTXT
  -e BATTLESPADES_REGION=europe \
  -e AOS_PUBLIC_HOST=203.0.113.10 \
  -e AOS_MASTER_WRITE_TOKEN=aos_srv_... \

Behind a UDP relay (for example a Playit endpoint 147.185.221.26:56675 forwarding to the container's 27015/udp), keep BATTLESPADES_PORT=27015 and set AOS_PUBLIC_HOST=147.185.221.26 and AOS_PUBLIC_PORT=56675. A2S keeps answering on the private socket while heartbeats and join tickets use the public endpoint.

Use your own config.toml

To change keys the variables do not cover (rotation, game rules, bots, conduct…), mount a complete config file and point the template at it. The environment variables are still applied on top, Steam is still forced off, and bans_path is always /data/bans.json.

Mount a templateTXT
docker run -d --name battlespades --restart unless-stopped \
  -p 27015:27015/udp \
  -v "$PWD/my-config.toml:/config/config.toml:ro" \
  -e BATTLESPADES_CONFIG_TEMPLATE=/config/config.toml \
  -e BATTLESPADES_ADMIN_PASSWORD='replace-with-a-long-secret' \
  -v battlespades-data:/data \
  ghcr.io/kikots/battlespades:main

Several servers with Compose

deploy/docker-compose.example.yml in the repository runs three hardened servers (CTF on 27015, TDM on 27025, Zombie on 27035) from one image: read-only root filesystem, all Linux capabilities dropped, no-new-privileges, a 256-process limit, a 64 MB /tmp, and one named volume per server.

First installation on a Linux nodeTXT
sudo git clone https://github.com/KikoTs/BattleSpades.git /opt/BattleSpades
cd /opt/BattleSpades
sudo cp deploy/.env.example deploy/.env
sudo chmod 600 deploy/.env
sudoedit deploy/.env     # SERVER_1..3_ADMIN_PASSWORD, AOS_PUBLIC_HOST, tokens

docker compose --env-file deploy/.env \
  -f deploy/docker-compose.example.yml up -d

The .env file (template: deploy/.env.example) holds BATTLESPADES_IMAGE, SERVER_1_ADMIN_PASSWORD … SERVER_3_ADMIN_PASSWORD, AOS_PUBLIC_HOST, optional AOS_MASTER_URL, BATTLESPADES_IMAGE_TAG, and AOS_SERVER_1_TOKEN … AOS_SERVER_3_TOKEN. Leave the tokens empty for the first boot, register each port, then fill them in and recreate only that service.

Updating containers safely

Pin production to an immutable sha-… tag rather than main. deploy/rollout.sh pulls a tag, recreates one server at a time, and waits for a valid protocol-168 A2S reply before moving on; if a server fails that health gate the rollout stops and prints its recent logs.

Roll the Compose fleet to a tested imageTXT
cd /opt/BattleSpades
./deploy/rollout.sh sha-1a2b3c4

Common questions

Can I run the image on ARM?

The published image is linux/amd64 only. On ARM hosts use a portable linux-arm64 release or build the image yourself with docker build.

Where are the logs?

docker logs <container> shows the console; files are in the volume under /data/logs/.

How do I build the image myself?

From a checkout: docker build --build-arg VCS_REF="$(git rev-parse HEAD)" --build-arg IMAGE_VERSION="$(cat VERSION)" -t battlespades:local .

Does the container support Steam browser listing?

No. The Steam helper needs Valve's Windows x86 runtime and is always disabled in the Linux image. AoSPlay listing works normally.