gaming servers
  • Shell 61.5%
  • Dockerfile 38.5%
Find a file
Jesse Garcia 91cfa55826
Some checks failed
ci/woodpecker/manual/woodpecker/1 Pipeline failed
ci/woodpecker/manual/woodpecker/2 Pipeline failed
README
2026-09-10 19:36:37 -06:00
scripts updates 2026-09-10 19:17:43 -06:00
.dockerignore +woodpecker 2026-09-10 19:30:34 -06:00
.gitignore wip 2026-03-09 09:45:23 -06:00
.justfile wip 2026-03-09 09:45:23 -06:00
.woodpecker.yml +woodpecker 2026-09-10 19:30:34 -06:00
compose.yml +woodpecker 2026-09-10 19:30:34 -06:00
Dockerfile wip 2026-03-09 09:45:23 -06:00
README.md README 2026-09-10 19:36:37 -06:00
renovate.json +woodpecker 2026-09-10 19:30:34 -06:00

frost

Containerized dedicated game servers for the homelab, built on steamcmd.

A single multi-stage Dockerfile produces one image per game. Each stage installs the game's runtime dependencies, ships a small entrypoint script that pulls the latest build from Steam on container start, and then execs the server binary. Images are published to oci.bvoy.dev/homelab/frost by Woodpecker CI.

Target Steam App ID Ports Status
valheim 896660 2456-2457/udp Working
astroneer 728470 8777/udp WIP — the entrypoint installs the app but does not yet launch the server

Layout

Dockerfile          multi-stage build; one stage (target) per game
compose.yml         local/homelab deployment
scripts/*.sh        per-game entrypoints (steamcmd update + server launch)
.justfile           helper recipes for poking at containers
.woodpecker.yml     CI: matrix build of every target via kaniko
.env                deployment config (gitignored)

Inside the container everything lives under SERVER_ROOT=/var/lib/frost:

  • /var/lib/frost/servers/<game> — game files installed by steamcmd
  • /var/lib/frost/saves — world/save data

Both are bind-mounted from the host so that game updates and worlds survive a container rebuild.

Configuration

.env is not committed. Create one alongside compose.yml:

STORAGE_SAVES=/mnt/tardis/pool/data/frost/steam
STORAGE_SERVERS=/mnt/tardis/pool/data/frost/servers

# valheim
SERVER_NAME=OGABOGA
WORLD_NAME=OGABOGA
WORLD_PASSWORD=CHANGEME

# published image tag; pin to <target>-<commit-sha> to roll back
FROST_TAG=valheim
Variable Default Purpose
STORAGE_SAVES Host path bind-mounted to /var/lib/frost/saves
STORAGE_SERVERS Host path bind-mounted to /var/lib/frost/servers
STEAM_USER anonymous Steam account used by steamcmd
SERVER_NAME OGABOGA Server name shown in the browser
WORLD_NAME OGABOGA Valheim world to load or create
WORLD_PASSWORD Valheim join password (min. 5 characters)
FROST_TAG valheim Image tag to pull

Running

Pull and run the published image:

docker compose up -d valheim
docker compose logs -f valheim

Build locally instead of pulling:

docker compose build valheim
docker compose up -d valheim

The entrypoint runs steamcmd +app_update … validate on every start, so the first boot takes a while and each restart picks up the latest game build.

just recipes

just                       # list recipes
just start <service>       # run ./start.sh in a throwaway container
just exec <service> [cmd]  # shell into a running container (default: sh)
just run <service> [cmd]   # one-off container with sh as the entrypoint
just login [user] [pass]   # run steamcmd +login, e.g. to handle Steam Guard

The recipes default to a steamcmd service; pass a service defined in compose.yml (e.g. just exec valheim) for anything real.

CI

.woodpecker.yml builds every target in the TARGET matrix with kaniko on pushes to main (and on manual/deployment events), pushing two tags per target:

  • oci.bvoy.dev/homelab/frost:<target> — moving tag
  • oci.bvoy.dev/homelab/frost:<target>-<commit-sha> — immutable, use it in FROST_TAG to roll back

Registry credentials come from the DOCKER_AUTH_CONFIG secret. Renovate keeps base images and CI images up to date.

Adding a game

  1. Add a stage to the Dockerfile off base, installing whatever runtime libs the server needs and EXPOSE-ing its ports.
  2. Drop an entrypoint in scripts/<game>.sh that runs steamcmd +app_update <appid> validate and then execs the server binary.
  3. Add the stage name to the TARGET matrix in .woodpecker.yml.
  4. Add a service to compose.yml pointing at the new target.