`maps *` ran on every panel open, dumping ~350 .bsp lines into the feed and evicting real messages. Filter bare .bsp lines from the feed, cache the map list per server (30 min TTL), and backfill 2500 raw log lines on start so history shows prior messages after filtering noise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CS 1.6 ReHLDS — self-contained image for many servers on Dokploy
A single reusable Docker image that runs ReHLDS by itself — engine (ReHLDS) + gamedll (ReGameDLL_CS) + Metamod-r + ReUnion (non-steam) are baked in. Deploy it as many times as you want; each server boots a working non-steam CS 1.6 server on first launch, then you upload that server's plugins / maps / configs over SFTP.
Layout
| File | Purpose |
|---|---|
rehlds/ |
the self-contained image (Dockerfile + baked configs) |
docker-compose.yml |
one game server (rehlds + fastdl + sftp) — deploy per server |
docker-compose.infra.yml |
shared MariaDB + Adminer + fastdl + autoheal — deploy once |
docker-compose.admin.yml |
web admin UI (dashboard + RCON console + live feed) — deploy once |
admin/ |
the admin UI service (Node) |
.env.example / .env.infra.example / .env.admin.example |
per-server / shared / admin settings |
fastdl/nginx.conf |
fastdl (asset-only, secrets denied) |
rehlds/compress-fastdl.sh |
pre-compress fastdl assets to .bz2 (baked into image) |
Each server = its own isolated cstrike volume + its own ports. Admin = Dokploy
(deploy/logs/CPU/restart) + Adminer (DB) + your in-game AMXX menu once you add AMXX.
Baked versions (pinned)
ReHLDS 3.15.0.896 · ReGameDLL 5.30.0.814 · Metamod-r 1.3.0.149 · ReUnion 0.2.0.25
Base: hldsdocker/cstrike:steam_legacy. Bump the *_URL ARGs in rehlds/Dockerfile to update.
One-time setup
Both stacks attach to dokploy-network (Dokploy creates this automatically). For a
plain (non-Dokploy) host, create it once: docker network create dokploy-network.
cp .env.infra.example .env.infra # set DB passwords
docker compose -f docker-compose.infra.yml up -d # shared DB + Adminer
On Dokploy: deploy docker-compose.infra.yml as a Compose app (git source, compose
path docker-compose.infra.yml).
Add a server
cp .env.example .env # set SERVER_NAME + unique PORT/FASTDL_PORT/SFTP_PORT
docker compose up -d --build # first app build tags cs16-rehlds:latest
On Dokploy: Create → Compose per server, same repo, different env:
| Server | PORT | FASTDL_PORT | SFTP_PORT |
|---|---|---|---|
| 1 | 27015 | 27080 | 2222 |
| 2 | 27016 | 27081 | 2223 |
| 3 | 27017 | 27082 | 2224 |
Each app gets its own cstrike volume automatically. To avoid rebuilding the image in
every app, build once and push to a registry (GHCR/Docker Hub), then set
image: <registry>/cs16-rehlds:latest and drop the build: block.
Add your files (per server, over SFTP)
Connect: sftp -P <SFTP_PORT> csadmin@YOUR_VPS_IP (password from .env). You land in
cstrike/. Upload:
- AMX Mod X →
addons/amxmodx/…, then uncomment its line inaddons/metamod/plugins.ini. - Plugins (
.amxx) →addons/amxmodx/plugins/+ list them inaddons/amxmodx/configs/plugins.ini. - Maps →
maps/, custom wads/models/sounds → their dirs. - Server-specific
server.cfg,mapcycle.txt,motd.txt, ban files — overwrite as needed.
Restart the server (Dokploy button) to load new metamod modules.
Database (per server)
AMXX plugins that use MySQL read addons/amxmodx/configs/sql.cfg. Point it at the shared DB:
amx_sql_host "db"
amx_sql_user "amx"
amx_sql_pass "<DB_PASSWORD>"
amx_sql_db "amx_1" # give each server its own DB (create it in Adminer), or share one
amx_sql_type "mysql"
Import an existing dump via Adminer (:8081) or:
gunzip < dump.sql.gz | docker compose -f docker-compose.infra.yml exec -T db mysql -uamx -p amx_1
Fast download
Set FASTDL_URL in each server's .env. Pre-compress assets so clients download faster:
docker compose exec rehlds compress-fastdl
(fastdl serves only game assets — server.cfg, sql.cfg, addons/, logs are denied.)
Firewall (per server)
PORT/udp— game (public)FASTDL_PORT/tcp— fastdl (public or behind Traefik)SFTP_PORT/tcp— sftp (restrict to your IP)8081/tcp— adminer (restrict to your IP)3306— never expose (DB internal only)
Reusing files from your old backup
Your cs-qgs-backup tar holds a full working server. To seed a server with it, just
SFTP the relevant pieces (its addons/, maps/, *.cfg, *.wad) into that server's
volume — no need to mount the whole 3.6 GB tree. The baked engine already matches
(ReHLDS/ReGameDLL/Metamod-r/ReUnion), so your compiled .amxx plugins keep working.
Health & auto-restart
Each game container has an A2S healthcheck (healthcheck.sh) — it's healthy only
while the engine answers queries, so a hung server (not just a crashed one) is caught.
restart: unless-stopped handles crashes; the autoheal service in the infra stack
watches every container labeled autoheal=true and restarts any Docker marks unhealthy.
Check state: docker ps → STATUS column shows (healthy) / (unhealthy).
Admin UI (all servers)
One web dashboard for every server — live status (map, players, ping), a per-server
RCON console with a real-time in-game feed (chat, connects, kills, map changes),
one-click kick / ban / change map / broadcast, and a player-stats page (unique
players per server per day, date-pickable, with the exact nickname list). Node service in
admin/, deploy once. It auto-discovers the running ReHLDS containers and the MariaDB
via the Docker socket — no server list to maintain.
cp .env.admin.example .env.admin # set ADMIN_USER/PASS, ADMIN_RCON_PASSWORD, DB_PASSWORD
docker compose -f docker-compose.admin.yml up -d --build
On Dokploy: Create → Compose, compose path docker-compose.admin.yml. In the app's
Environment set ADMIN_USER, ADMIN_PASS, ADMIN_RCON_PASSWORD, DB_PASSWORD. Add domain
admin.thealphaspot.com → HTTPS, container port 3000.
- Discovery: the backend lists containers over the mounted
docker.sock(read-only), matches thecs16-rehldsimage, and reaches each one by its dokploy-network IP + internal port. New servers appear automatically (~20s). The MariaDB is discovered the same way. - Live feed: the console streams each game container's stdout straight off the Docker
socket (
docker logs -f, same as a game panel) — full history + live chat/kills/map changes, nologaddress. The RCON password echoed in that stdout is redacted before it reaches the browser. SetDEBUG_FEED=1to log received console lines on the admin. - Player stats: every 5 min it samples each server's
status, skips bots, and UPSERTs each nickname intoadmin_player_seen(one row per server+player+day) in the amx DB. - Auth: built-in HTTP Basic (
ADMIN_USER/ADMIN_PASS) covers HTTP + the WebSocket; put HTTPS in front via the Dokploy domain. RCON/DB passwords never reach the browser. - Security note: mounting
docker.sockgrants the container control of the Docker daemon (root-equivalent). It's read-only and only used for GET calls here, but keep the UI locked.
Notes
- Non-steam works out of the box (ReUnion,
cid_NoSteam47/48 = 3). Overridereunion.cfgvia SFTP if you want a different auth scheme. - Skip Pterodactyl/Pelican/cstrikeCP — they bring their own daemon that fights Dokploy for the Docker socket. Here Dokploy is the only orchestrator.