4.4 KiB
date-created
| date-created |
|---|
| 2026-09-10 12:01 |
SteamCMD server setup
This note explains how to add a SteamCMD-managed game server to the NixOS configuration.
Use these existing files as examples:
configs/nixos/modules/services/steamcmd.nixconfigs/nixos/modules/services/games/valheim.nixconfigs/nixos/guests/valheim.nixconfigs/nixos/hosts/pikachu/default.nix
Understand the layers
The shared steamcmd module installs and runs a dedicated server.
It owns the low-level systemd service, update service, user, directories, and firewall ports.
A game module maps game-specific options onto modules.services.steamcmd.
It hides Steam app IDs, executable names, launch arguments, and port calculations.
A guest module maps host-level storage, secrets, and resource limits onto the game module. It keeps host paths outside the guest's interior configuration.
A host enables the guest and supplies network placement, storage paths, and routing.
Create the game module
- Create
configs/nixos/modules/services/games/<game>.nix. - Add an option namespace at
modules.services.games.<game>. - Add an
enableoption withlib.mkEnableOption. - Add options for game-specific settings.
- Add options for
steamDir,installDir, andstateDir. - Use
lib.mkIf config.modules.services.games.<game>.enablefor the module configuration. - Set
modules.services.steamcmd.enable = true. - Pass
steamDir,installDir, andstateDirtomodules.services.steamcmd. - Set
serviceNameto the game service name. - Set
appIdto the Steam dedicated-server app ID. - Set
executableto the server executable or a generated wrapper script. - Set
argumentsto the game launch arguments. - Set
udpPortsto every UDP port the server needs. - Put secrets in runtime files instead of option values.
- Read secret files from a wrapper script when the server needs secret values.
Use configs/nixos/modules/services/games/valheim.nix as the main pattern.
It wraps the executable because Valheim receives its password as a command-line argument.
Create the guest module
- Create
configs/nixos/guests/<game>.nix. - Add an option namespace at
guests.<game>. - Add host-path options for the SteamCMD metadata, install, and state directories.
- Give host-path options a
nulldefault. - Add assertions that require each host path when the guest is enabled.
- Define guest-internal paths in a
letblock. - Enable
guests.<game>.interior.modules.services.games.<game>. - Pass the guest-internal paths to the game module.
- Add mounts that bind host paths to the guest-internal paths.
- Set directory mount
owner,group, andmodetogether. - Mount secret files under
/run/secrets/...when the game needs secrets. - Declare matching
sops.secretsentries for those secret files. - Set conservative default resource limits under
guests.<game>.limits.
Use configs/nixos/guests/valheim.nix as the main pattern.
It mounts the password secret into the guest and passes only the runtime path inward.
Enable the guest on a host
- Open the target host file under
configs/nixos/hosts/<host>/default.nix. - Set
guests.<game>.enable = true. - Set
vlanandaddressfor the guest. - Set host storage paths for SteamCMD metadata, server files, and state.
- Set game options such as server name and port.
- Enable reverse-proxy routing when the server needs external access.
- Route every game UDP port exposed by the guest.
- Add required secrets to the host's SOPS files.
Use the Valheim section in configs/nixos/hosts/pikachu/default.nix as the host-level pattern.
Test the change
-
Stage new Nix files before evaluation. Flake evaluation ignores untracked files.
$ git add configs/nixos/modules/services/games/<game>.nix configs/nixos/guests/<game>.nix -
Build the target host configuration.
$ nixos-rebuild build --flake .#<host> -
Deploy the host when the build succeeds.
$ sudo nixos-rebuild switch --flake .#<host> -
Enter the guest from the host.
$ sudo machinectl shell <game> -
Check the update service inside the guest.
$ systemctl status steamcmd-<game>-update.service -
Check the game service inside the guest.
$ systemctl status <game>.service -
Inspect the logs when either service fails.
$ journalctl -u steamcmd-<game>-update.service -u <game>.service