
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.
The image
| Property | Value |
|---|---|
| Registry | ghcr.io/kikots/battlespades |
| Tags | main (latest successful build of main), sha-<commit> (immutable, use for production), and v… release tags |
| Platform | linux/amd64 |
| Built by | .github/workflows/container.yml — only after the complete test suite passes; images carry provenance and an SBOM |
| Base | python:3.12-slim-bookworm, compiled in a separate build stage |
| Exposed port | 27015/udp |
| User | UID/GID 10001 (battlespades); only /data is writable |
| Entrypoint | tini → 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
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, setBATTLESPADES_PORT=27025and publish-p 27025:27025/udp. BATTLESPADES_REVIVAL_ENABLED=falsekeeps 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.
python deploy/a2s_probe.py --host 127.0.0.1 --port 27015Environment 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.
| Variable | Applies to | Effect |
|---|---|---|
BATTLESPADES_SERVER_NAME | Docker image | Sets [server] name (up to 64 characters accepted; clients show 31). |
BATTLESPADES_PORT | Docker image | Sets [server] port, the UDP game and A2S port (1–65535). |
BATTLESPADES_MAX_PLAYERS | Docker image | Sets [server] max_players (1–255). |
BATTLESPADES_MODE | Docker image | Sets [game] default_mode (a mode code or alias). |
BATTLESPADES_MAP | Docker image | Sets [game] default_map. A map name, never a path. |
BATTLESPADES_BOT_COUNT | Docker image | Fixed bot population (0–254): sets [bots] to fixed with this many bots, or disables bots at 0. |
BATTLESPADES_REGION | Docker image | Sets [revival] region. |
BATTLESPADES_OFFICIAL | Docker image | Sets [revival] official (true/false, yes/no, on/off, 1/0). |
BATTLESPADES_REQUIRE_IDENTITY | Docker image | Sets [revival] require_identity. |
BATTLESPADES_REVIVAL_ENABLED | Docker image | Sets [revival] enabled. Use false for a private test container. |
BATTLESPADES_ADMIN_PASSWORD | Docker image | Sets [admin] password; must be at least 12 characters. The container refuses to start while the password is still changeme. |
BATTLESPADES_ALLOW_INSECURE_DEFAULTS | Docker image | Set to true only to start a throwaway container with the sample changeme password. |
BATTLESPADES_DATA_DIR | Docker image | Writable data directory, /data or below (default /data). Holds the generated config, logs and bans.json. |
BATTLESPADES_CONFIG_TEMPLATE | Docker image | TOML 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_CONFIG | Docker image | Where the effective config is written (default /data/runtime/config.toml). |
AOS_MASTER_WRITE_TOKEN | any launch | The 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_URL | any launch | Overrides [revival] base_url. |
AOS_PUBLIC_HOST | any launch | Overrides [revival] public_host: the public IPv4 players connect to. |
AOS_PUBLIC_PORT | any launch | Public UDP game port when it differs from the listen port (router mapping or a relay such as Playit). |
AOS_PUBLIC_QUERY_PORT | any launch | Public A2S port. Defaults to the public game port when AOS_PUBLIC_PORT is set. |
AOS_SERVER_ID | any launch | Overrides [revival] server_id; must equal public_host:public_port. |
BATTLESPADES_STEAM_RUNTIME | any launch | Fallback for [steam] runtime_dir. |
BATTLESPADES_STEAMCLIENT_RUNTIME | any launch | Fallback for [steam] steamclient_dir. |
BATTLESPADES_STEAM_HELPER | any launch | Fallback for [steam] helper_path. |
Make a container public
- 01Open UDP
Allow the published UDP port in the VPS provider's firewall and the host firewall.
- 02First boot without a token
Start with
AOS_PUBLIC_HOSTset to the node's public IPv4 and noAOS_MASTER_WRITE_TOKEN. - 03Register on the node
Run
deploy/register_server.pyon the game node once the A2S probe passes (see Ports and public listing). - 04Recreate with the token
Put the printed
aos_srv_…token inAOS_MASTER_WRITE_TOKENand recreate only that container.
-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.
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:mainSeveral 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.
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 -dThe .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.
cd /opt/BattleSpades
./deploy/rollout.sh sha-1a2b3c4Common 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.


