skip to content
wihan.dev

Running a Factorio server with Factorio Server Manager and Docker

One container, one compose file, and a web UI for mods, saves, and restarts. From a box that has Docker to a factory your friends can join.

3 minfactorio · docker · compose

The Factorio server behind this site is one container and one compose file. Factorio Server Manager runs inside it: a web UI that downloads the headless server, manages saves and mods, and starts and stops the game process. Server administration is a browser tab.

Credit where it is due: Factorio Server Manager is Tricade’s work, carrying on the OpenFactorioServerManager project. Everything pleasant about running this server - the mod portal login, the save management, the graceful shutdown - is their code. Star the repo if you run a factory.

Here’s the full path, from a box that has Docker to a factory your friends can join.

1. Write the compose file

This is the whole deployment:

yaml
services:
  manager:
    image: ghcr.io/tricade/factorio-server-manager:0.17.5
    restart: unless-stopped
    stop_grace_period: 3m
    environment:
      FACTORIO_VERSION: stable
      FSM_COOKIE_SECURE: "true"
    ports:
      - "34197:34197/udp"
    volumes:
      - fsm-data:/opt/fsm-data
      - factorio-data:/opt/factorio

volumes:
  fsm-data:
  factorio-data:

Every line that is not boilerplate earned its place:

  • Pin the image to an exact release tag. The manager only changes when you decide it does.
  • Named volumes. factorio-data holds the game and saves, fsm-data holds the manager state. Explicit names keep the compose project prefix out, so a backup job can address them directly.
  • FSM_COOKIE_SECURE: "true", because the UI will only ever be served over HTTPS.

⚠️ Do not skip stop_grace_period: 3m. On SIGTERM the manager asks Factorio for a clean save-and-stop and needs up to 165 seconds. The Docker default of 10 seconds kills the game mid-save, which is how maps die.

Then bring it up:

bash
docker compose up -d

2. Point DNS at it - carefully

The UI rides your reverse proxy over HTTPS like any other web app. Game traffic does not: it is UDP on 34197, and an HTTP proxy like Traefik cannot route it. That’s why the compose file publishes the port straight on the host.

It also means the DNS record for the game must be a DNS-only A record pointing at the machine.

⚠️ A CNAME to a proxied hostname resolves to the Cloudflare edge, which accepts your packets and tells no one. If the UI loads fine but nobody can join, this is the first thing to check.

If a firewall fronts the box, open the game port explicitly:

bash
sudo ufw allow 34197/udp

3. Log in and burn the bootstrap credential

The manager generates an admin credential on first start and writes it into the volume. Read it out:

bash
docker compose exec manager cat /opt/fsm-data/initial-admin-password.txt

Log in with it, change the password, and the file deletes itself. Until you do, the bootstrap credential is lying around - do this first, not eventually.

4. Install the game and pin the version

From the UI, download the headless server and create (or upload) a save. Version pinning matters twice here:

  • The manager image is pinned to an exact release tag in the compose file.
  • The Factorio version is pinned in the UI after install, because the server version must match the players’ clients. stable in the compose file is only the first-install default.

Mods, save uploads, and restarts all happen in the UI from here on. The compose file changes only when the manager itself gets an upgrade, which is exactly how often I want to think about it.

5. Back up the volumes

Because the volumes have explicit names, a backup is one command with no compose context needed:

bash
docker run --rm -v factorio-data:/data -v "$PWD":/backup \
  alpine tar czf /backup/factorio-data.tgz -C /data .

Run it on a schedule, and test a restore once before you trust it.

Verify

bash
docker compose ps        # ✅ the manager container is up
ss -lun | grep 34197     # ✅ the game port listens on UDP

Then join from a Factorio client: Multiplayer → Connect to address with your-host:34197. If the UI works but the join times out, go back to step 2 - it’s almost always the proxied DNS record.