How to Run a CS 1.6 Server in Docker

July 2, 2025 Daemon666 9 min read 16 просмотров

Docker is a genuinely good fit for CS 1.6: the engine is a fixed 32-bit binary with a small, well-known set of dependencies, so an image is reproducible and disposable in a way a hand-built VPS install never is. The one thing that trips people up is networking — a game server that answers A2S queries does not behave like a web app behind a port map, and getting that wrong is why a container "runs fine" but never shows in the server list.

1. A Dockerfile with the 32-bit runtime baked in

HLDS is a 32-bit ELF binary, so the image must carry the i386 runtime. Build on a slim Debian base:

FROM debian:12-slim

RUN dpkg --add-architecture i386 \
 && apt-get update \
 && apt-get install -y --no-install-recommends \
      lib32gcc-s1 lib32stdc++6 libc6:i386 libncurses6:i386 \
      libcurl4:i386 ca-certificates tar wget \
 && rm -rf /var/lib/apt/lists/*

RUN useradd -m -u 1000 steam
USER steam
WORKDIR /home/steam

RUN mkdir -p steamcmd && cd steamcmd \
 && wget -q https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \
 && tar -xzf steamcmd_linux.tar.gz && rm steamcmd_linux.tar.gz

The exact i386 package names are the same ones covered in the 32-bit library guide; if a base image is newer or older, that guide lists the alternates.

2. Install App 90 at build time or on a volume

You can bake the game into the image, but the cleaner pattern is to install it onto a named volume the first time the container runs, so image rebuilds do not re-download several hundred megabytes. Add an entrypoint script:

#!/bin/sh
set -e
cd /home/steam/steamcmd
if [ ! -f /home/steam/hlds/cstrike/liblist.gam ]; then
  ./steamcmd.sh +force_install_dir /home/steam/hlds \
    +login anonymous +app_set_config 90 mod cstrike \
    +app_update 90 validate +quit
fi
exec /home/steam/hlds/hlds_run -game cstrike \
  -ip 0.0.0.0 -port 27015 +sv_lan 0 \
  +map de_dust2 +maxplayers "${MAXPLAYERS:-20}"

App 90's anonymous download often needs a second pass; if cstrike/ is incomplete after the first run, delete the volume's marker and let it re-validate. This is the same quirk described in the SteamCMD guide.

3. Get the networking right — use host networking

This is the part that actually matters. CS 1.6 answers Steam A2S queries and heartbeats the master list from the same UDP socket it serves game traffic on. Under Docker's default bridge network with a published port, the source address/port of the server's outbound replies gets rewritten by the NAT layer, and the master server and some clients then see a mismatch and drop the server from the list. The reliable fix is host networking:

docker run -d --name cs16 \
  --network host \
  -e MAXPLAYERS=20 \
  -v cs16-data:/home/steam/hlds \
  cs16-server

With --network host the container shares the host's network stack, UDP 27015 is bound directly, and A2S/heartbeat traffic has the correct source. If you cannot use host networking (for example on a platform that forbids it), you must publish UDP and keep the same internal and external port number, and expect to fight master-list visibility:

docker run -d --name cs16 \
  -p 27015:27015/udp -p 27015:27015/tcp \
  -v cs16-data:/home/steam/hlds \
  cs16-server

4. docker-compose for something you will keep

services:
  cs16:
    build: .
    network_mode: host
    environment:
      - MAXPLAYERS=20
    volumes:
      - cs16-data:/home/steam/hlds
    restart: unless-stopped
volumes:
  cs16-data:

restart: unless-stopped replaces the systemd unit you would otherwise write on bare metal.

Common errors

  • Container runs, server never appears in the internet list — you are on bridge networking with port publishing. Switch to --network host. This is the single most common Docker-specific failure.
  • ./hlds_linux: No such file or directory — the i386 runtime is missing from the image. That ELF-interpreter error means /lib/ld-linux.so.2 is absent; add the i386 packages to the Dockerfile.
  • Players connect but the server ignores rcon — RCON is TCP; under bridge networking you only published UDP. Publish TCP 27015 too, or use host networking.
  • Re-downloads the game every start — you baked the install into the image layer instead of a volume, or the entrypoint's "already installed" check does not match your path.

Verification

docker logs -f cs16

Watch for the map load and the Connection to Steam servers successful line. Then from outside the host, query it and connect with a client to HOST_IP:27015. If it answers A2S but does not list, revisit networking — with host mode it will list. From there, layer on ReHLDS by building it into the image instead of the stock engine.

Участники: Daemon666 ✦
Поделиться: