The classic Call of Duty dedicated servers are 32-bit binaries from 2003–2008. Docker is a good fit for them: you get the ancient 32-bit runtime in a container instead of on your host, and you can run a dozen instances off one set of game files.
The base image
Everything these binaries need is a handful of i386 libraries:
FROM ubuntu:jammy
RUN dpkg --add-architecture i386 && \
apt-get update && \
apt-get install -y --no-install-recommends \
libc6:i386 libgcc-s1:i386 lib32stdc++6 libstdc++5:i386 && \
rm -rf /var/lib/apt/lists/*
WORKDIR /server
That covers CoD1, UO and CoD2. For CoD4X add zlib1g:i386, libcurl4:i386 and the
32-bit OpenSSL runtime.
libstdc++5:i386 is only needed by the older binaries but costs nothing to include, and
its absence is the single most common startup failure. See
libstdc++.so.5 missing.
Build it once and reuse it for every game:
docker build -t cod/engine .
Host networking, not port mapping
Use network_mode: host. These engines advertise themselves to the master server, and the
address the master records is the source address of the heartbeat. Behind Docker's default
NAT bridge, the port the master sees may not be the port you published, and clients then get
an unreachable address from the browser.
Host networking sidesteps that entirely. It also means net_port is the real port, so run
each instance on its own.
Compose file
services:
cod2:
image: cod/engine
network_mode: host
restart: unless-stopped
security_opt: [seccomp=unconfined]
working_dir: /server
volumes:
- ./server:/server
- /srv/cod-paks:/srv/cod-paks:ro
command: >
setarch i386 -R ./cod2_lnxded
+set fs_basepath /server +set fs_homepath /server
+set dedicated 2 +set net_port 28960
+set rcon_password change-me
+exec server.cfg
+map mp_toujane
Three details worth understanding:
setarch i386 -R disables address-space randomisation for the process. These binaries
predate ASLR and a few of them fault intermittently without it. It costs nothing to include.
security_opt: [seccomp=unconfined] is needed because the default seccomp profile
blocks some of the older syscalls these 32-bit binaries make. If you would rather not
relax seccomp globally, profile the container and write a narrower profile — but start
here and confirm it runs first.
The read-only pak mount lets many containers share one copy of the game files. Paks
never change; only server.cfg, logs and mod paks do, and those live in the per-instance
./server directory.
Sharing game files across instances
A useful layout for several servers on one host:
/srv/cod-paks/cod2/main/iw_00.iwd … # shared, read-only
/srv/servers/cod2-pub-28960/server/
├── cod2_lnxded # or a symlink
├── main/
│ ├── iw_00.iwd -> /srv/cod-paks/cod2/main/iw_00.iwd
│ ├── server.cfg
│ └── zz_mymod.iwd
└── docker-compose.yml
/srv/servers/cod2-match-28963/server/…
Symlink the stock paks in, keep per-server files real. Each instance is then a directory with a compose file, and adding a server is a copy plus a port change.
Port allocation
Give each instance room. Some engine generations use adjacent ports for auxiliary traffic, so spacing instances 3 ports apart avoids surprises:
28960 cod2-pub
28963 cod2-match
28966 cod4-pub
Logs
With restart: unless-stopped and host networking, docker logs is your console:
docker compose logs -f --tail 50 | grep -v 'cmdCount\|MAX_PACKET\|Rcon from'
Filter those three patterns always. They are per-tick noise from NAT and from clients sending user commands faster than the server samples them, and they will bury anything real. More on reading logs in the rcon guide.
Restarting after a config change
The bind mount means editing server.cfg on the host is instant. Applying it is either an
rcon exec server.cfg (for cvars that take effect live) or:
docker compose restart
Copying a new mod pak in and restarting is atomic enough for iteration — the container re-reads the gamedir on start.
Memory footprint
For capacity planning on a small VPS:
| Game | Approx RSS per instance |
|---|---|
| Call of Duty 1 / UO | ~64 MB |
| Call of Duty 2 | ~100 MB |
| Call of Duty 4 | ~200 MB |
| World at War | ~250 MB |
CPU is negligible at 20 sv_fps with 20 players. A 2 GB VPS comfortably runs several
CoD1/CoD2 servers.
