Installation
Getting the panel into production with Docker Compose, in a handful of commands.
Requirements#
- Docker and the Compose plugin;
- a headless Factorio server with RCON enabled — the
docker-compose.ymlbelow provides one; - nothing else: the panel ships its own SQLite storage
(
node:sqlite, built into Node, no native dependency to compile).
Quick start, without cloning#
The images are published, so the repository is optional: one directory, the
docker-compose.yml below pasted as it stands, and a generated
.env next to it. Both the game server and the panel come up with
docker compose up.
services:
factorio:
container_name: factorio-server
image: factoriotools/factorio:stable
restart: unless-stopped
ports:
# Game port only. RCON stays unpublished: the panel reaches it over the
# compose network, and the port hands full server control to whoever
# holds the password.
- "34197:34197/udp"
volumes:
- ./data:/factorio
environment:
- UPDATE_MODS_ON_START=true
factorio-admin:
container_name: factorio-admin-panel
# A minor tag: patches arrive, a major never lands on you by surprise.
# In production, pin the exact version instead.
image: williamnauroy/factorio-admin-rcon:1.4-distroless
restart: unless-stopped
depends_on:
- factorio
ports:
# Loopback only: the panel grants full RCON access.
- "127.0.0.1:3010:3000"
- "[::1]:3010:3000"
volumes:
# The directory, not the file: a regenerated rconpw is picked up as is.
- ./data/config:/factorio-config:ro
# Sessions, audit log and metric series (SQLite).
- factorio-admin-data:/data
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=16m
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 256m
pids_limit: 128
environment:
- RCON_HOST=factorio
- RCON_PORT=27015
- RCON_PASSWORD_FILE=/factorio-config/rconpw
- ADMIN_PASSWORD=${ADMIN_PASSWORD:?set it in .env}
- SESSION_SECRET=${SESSION_SECRET:?set it in .env, 32 chars minimum}
# No docker-socket-proxy here, so the CPU and memory graphs are off.
# Players and UPS keep working.
- METRICS_DOCKER=false
volumes:
factorio-admin-data:The secrets are generated rather than typed — that is all
setup-admin.sh does in the repository:
cat > .env <<EOF
ADMIN_PASSWORD=$(openssl rand -base64 18)
SESSION_SECRET=$(openssl rand -hex 32)
EOF
docker compose up -d
cat .env # the password to log in with
# → http://127.0.0.1:3010MODERATOR_PASSWORD and VIEWER_PASSWORD are optional:
add them to .env and to the service's
environment: to open the two read-only roles. A role without a
password has no account at all.
data/config/rconpw does not exist yet. The panel re-reads that
file on every connection attempt, so it recovers on its own after a few
seconds — no restart needed.
Updating is a pull, since nothing is built locally: bump the tag, then
docker compose pull && docker compose up -d.
Production setup#
setup-admin.sh writes a .env file next to the
docker-compose.yml: one password per role and the session signing
key.
./setup-admin.sh # ADMIN_PASSWORD + SESSION_SECRET
./setup-admin.sh --all # all three roles
./setup-admin.sh --force --yes # full rotation, no questions askedThen:
docker compose up -d --build factorio-adminsetup-admin.sh generates it.
What gets mounted#
| Mount | Purpose |
|---|---|
./data/config:/factorio-config:ro | The RCON password (rconpw, regenerated by the game server) and, if you have one, your command catalogue. The directory is mounted rather than the file, so a recreated file is picked up without a restart. |
factorio-admin-data:/data | Named volume: sessions, audit log and metric series (SQLite). It is the only place the panel writes to. |
The container runs with a read-only root filesystem,
cap_drop: [ALL], no-new-privileges, and memory and PID
caps. /tmp is a tmpfs, purely as insurance.
Hardened image (optional)#
Dockerfile.distroless produces a variant with no shell and no
package manager, published under -distroless tags. It is the
variant the docker-compose.yml uses by default. It shrinks the
attack surface at the cost of less convenient debugging: no
docker exec … sh.
Behind a reverse proxy#
The panel only listens on the loopback interface. To expose it, put a TLS
reverse proxy in front and enable TRUST_PROXY:
TRUST_PROXY=trueTRUST_PROXY behind a proxy you control
It makes the panel trust X-Forwarded-For for per-IP rate limiting.
Exposed directly, anyone forges that header and resets their own counter on
every attempt. While it is false the header is ignored and rate
limiting falls back to a global bucket — coarser, but unforgeable.
The session cookie turns secure on its own as soon as the request
arrives over HTTPS (COOKIE_SECURE=auto, the default).
Health probes#
| Route | Answers | Use it for |
|---|---|---|
/api/health | 200 as long as the process is alive | Docker's liveness probe. It does not depend on Factorio. |
/api/ready | 200 when configuration, command catalogue, database and RCON all answer; 503 otherwise | The readiness probe. Kept separate from the above so a Factorio outage does not restart the panel in a loop. |
Updating#
git pull
docker compose up -d --build factorio-adminPublished images follow the versions semantic-release cuts
(williamnauroy/factorio-admin-rcon). The database schema is upgraded
at startup: columns added after the fact are caught up by an idempotent
ALTER TABLE, so there is nothing to do by hand.