Running Call of Duty dedicated servers in Docker

Updated 2026-07-25

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.

Frequently asked

Why does a Call of Duty server in Docker need host networking?

The master server records the source address of the heartbeat. Behind Docker's NAT bridge that address may not match the port you published, so clients get an unreachable entry from the browser. Host networking avoids the translation.

What is setarch i386 -R for?

It disables address space randomisation for the process. The classic CoD binaries predate ASLR and some of them fault intermittently without it.

Can several Call of Duty servers share one copy of the game files?

Yes. Mount the pak directory read-only and symlink the stock paks into each instance's main/ directory. Only server.cfg, logs and mod paks need to be per-instance.