Files
dev/docs/00 INBOX/how to setup a steamcmd server.md

4.5 KiB

date-created
date-created
2026-09-10 12:01

How to set up a steamcmd server

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.nix
  • configs/nixos/modules/services/games/valheim.nix
  • configs/nixos/guests/valheim.nix
  • configs/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

  1. Create configs/nixos/modules/services/games/<game>.nix.
  2. Add an option namespace at modules.services.games.<game>.
  3. Add an enable option with lib.mkEnableOption.
  4. Add options for game-specific settings.
  5. Add options for steamDir, installDir, and stateDir.
  6. Use lib.mkIf config.modules.services.games.<game>.enable for the module configuration.
  7. Set modules.services.steamcmd.enable = true.
  8. Pass steamDir, installDir, and stateDir to modules.services.steamcmd.
  9. Set serviceName to the game service name.
  10. Set appId to the Steam dedicated-server app ID.
  11. Set executable to the server executable or a generated wrapper script.
  12. Set arguments to the game launch arguments.
  13. Set udpPorts to every UDP port the server needs.
  14. Put secrets in runtime files instead of option values.
  15. 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

  1. Create configs/nixos/guests/<game>.nix.
  2. Add an option namespace at guests.<game>.
  3. Add host-path options for the SteamCMD metadata, install, and state directories.
  4. Give host-path options a null default.
  5. Add assertions that require each host path when the guest is enabled.
  6. Define guest-internal paths in a let block.
  7. Enable guests.<game>.interior.modules.services.games.<game>.
  8. Pass the guest-internal paths to the game module.
  9. Add mounts that bind host paths to the guest-internal paths.
  10. Set directory mount owner, group, and mode together.
  11. Mount secret files under /run/secrets/... when the game needs secrets.
  12. Declare matching sops.secrets entries for those secret files.
  13. 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

  1. Open the target host file under configs/nixos/hosts/<host>/default.nix.
  2. Set guests.<game>.enable = true.
  3. Set vlan and address for the guest.
  4. Set host storage paths for SteamCMD metadata, server files, and state.
  5. Set game options such as server name and port.
  6. Enable reverse-proxy routing when the server needs external access.
  7. Route every game UDP port exposed by the guest.
  8. 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

  1. 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
    
  2. Build the target host configuration.

    $ nixos-rebuild build --flake .#<host>
    
  3. Deploy the host when the build succeeds.

    $ sudo nixos-rebuild switch --flake .#<host>
    
  4. Enter the guest from the host.

    $ sudo machinectl shell <game>
    
  5. Check the update service inside the guest.

    $ systemctl status steamcmd-<game>-update.service
    
  6. Check the game service inside the guest.

    $ systemctl status <game>.service
    
  7. Inspect the logs when either service fails.

    $ journalctl -u steamcmd-<game>-update.service -u <game>.service