There are two honest reasons to build ReHLDS yourself. The first is trust: you are about to run a network-facing binary as a long-lived process, and "some .so from a forum post" is not a supply chain. The second is that you want a specific commit — a fix that is merged but not yet in a tagged release. Docker makes both reproducible, because the whole 32-bit toolchain lives in a throwaway container instead of on your host.
1. What ReHLDS actually needs to build
ReHLDS is a 32-bit C++ project. On Linux it builds with a 32-bit GCC/Clang toolchain, and it uses a Python-based build script. So the container needs:
gcc-multilib/g++-multilib(or a Clang with 32-bit support)python3git- the 32-bit development libraries (
libc6-dev-i386)
Nothing else. The project vendors what it needs.
2. A build container
Write Dockerfile.rehlds:
FROM debian:12
RUN dpkg --add-architecture i386 \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential gcc-multilib g++-multilib libc6-dev-i386 \
python3 python3-pip git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
CMD ["/bin/bash"]
Build it:
docker build -t rehlds-build -f Dockerfile.rehlds .
3. Clone and check out a known commit
Do this on the host so the source survives the container:
mkdir -p ~/build && cd ~/build git clone --recursive https://github.com/rehlds/rehlds.git cd rehlds git log --oneline -5
Use --recursive. ReHLDS pulls in submodules; a clone without them fails partway through the build with missing-header errors that look like a broken toolchain but are not. If you already cloned without it:
git submodule update --init --recursive
Pin what you build. Either check out the tag matching the latest release, or note the commit hash — an unpinned main is not reproducible and you will not be able to tell, six months later, what is actually on your server.
git checkout <tag-or-commit> git rev-parse HEAD > ../rehlds-commit.txt
4. Build inside the container
docker run --rm -it \ -v ~/build/rehlds:/src \ -w /src \ rehlds-build bash
Inside the container, invoke the project's build script. ReHLDS uses a Python build wrapper at the repository root; run it with the release configuration:
python3 build.py --clean
If the script name or flags differ in the revision you checked out, read the repository's own build documentation rather than guessing — the project has changed build systems more than once over its life, and inventing flags will only waste your afternoon.
On success the artefacts land in a build/ or publish/ directory. The one file you care about on Linux is:
engine_i486.so
5. Sanity-check the artefact before you ship it
A 64-bit build is the single most common outcome of a misconfigured multilib toolchain, and it fails at load time with a confusing error. Check the architecture explicitly:
file engine_i486.so
It must report:
ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked
If it says ELF 64-bit ... x86-64, the multilib packages were not actually used. Rebuild with the 32-bit flags forced, and do not deploy the 64-bit object — it will produce wrong ELF class: ELFCLASS64 at startup (see that error).
Check the dynamic dependencies too:
ldd engine_i486.so
Every entry must resolve. A not found line here means the target server will need that 32-bit library installed — see the 32-bit library guide.
6. Deploy
cd /home/steam/hlds cp engine_i486.so engine_i486.so.bak scp user@buildhost:~/build/rehlds/build/engine_i486.so ./engine_i486.so chmod 644 engine_i486.so ./hlds_run -game cstrike +map de_dust2 +maxplayers 20 +sv_lan 0
Then, in the console:
rehlds_version
It should print the version corresponding to what you built. Record the commit hash from rehlds-commit.txt next to the deployed binary — a year from now, "which ReHLDS is this?" is a question you will actually need to answer.
Common errors
fatal error: sys/cdefs.h: No such file or directory—libc6-dev-i386is missing from the container.skipping incompatible /usr/lib/x86_64-linux-gnu/... when searching for -lstdc++— the linker is finding 64-bit libraries for a 32-bit link.g++-multilibis not installed, or-m32is not being passed.- Missing headers from a submodule path — you cloned without
--recursive. Rungit submodule update --init --recursive. - Builds fine, server segfaults on start — you built for a different ABI than the rest of your stack, or mixed a self-built engine with a much older Metamod. Test on a staging server, never straight onto the public one.
Verification
file engine_i486.soreports ELF 32-bit, Intel 80386.lddresolves every dependency.- The server boots and
rehlds_versionanswers. - A client connects and plays for a full map without a crash.
Once that holds, wire it into your normal deploy. The same container pattern works for compiling ReGameDLL_CS, which is the natural next step — the two are versioned together and you want them from the same era.









