This directory contains game server configuration recipes think of them as templates or profiles for each supported game. Each recipe defines how to provision, configure, and run a specific game server!
Each game gets its own subdirectory with:
games/
├── minecraft/
│ ├── index.ts # Game definition & metadata
│ ├── install.ts # Docker image & installation setup
│ └── settings.ts # User-configurable settings schema
├── rust/
│ ├── index.ts
│ ├── install.ts
│ └── settings.ts
└── ...
index.ts— Exports the array of all supported games and helper functions (getGame,getDefaults, etc.)settings.ts— Type definitions and utilities for game settings (shared schema logic)compose.ts— Docker Compose builder utilities (UFW rules, port mapping, container names)steam.ts— SteamCMD integration for games that require Steam
The games repo can be validated standalone (no dependency on the main GSM repo tooling).
npm run fmt:check
npm run lint
npm run validate:recipes
npm run typecheckOr run all checks in one command:
npm run cimkdir games/yourserver
cd games/yourserverThis is the game definition. It exports a constant object with metadata, ports, resource requirements, and settings:
importtype{ComposeConfig}from"../compose";import{resolveSettings}from"../settings";import{buildYourServerCompose,dockerImage}from"./install";import{yourServerSettings}from"./settings";constbuildCompose=(config: ComposeConfig,raw: unknown): string=>buildYourServerCompose(config,resolveSettings(yourServerSettings,raw));exportconstyourserver={// Required metadataid: "yourserver",// Unique slug used in URLs/APIsname: "Your Server",// Display namedescription: "Your server desc",// Marketing textenabled: true,// Toggle visibility in UI// Game identificationgamedigId: "yourserver",// GameDig protocol ID for status checksimage: "/games/yourserver.png",// UI icon (200x200px PNG)// Docker & compose
buildCompose,// Function to generate docker-compose.yml
dockerImage,// Base container image reference// Network ports exposed to playersports: [{from: 25_565,// Container internal portto: 25_565,// External listening portprotocol: "tcp",// "tcp" or "udp"},// Add more ports as needed],// Minimum host requirementsrequirements: {cpu: 2,// vCPU countmemory: 8,// GBdisk: 10,// GB},// Settings UI schemasettings: yourServerSettings,// Auth behaviorusesJoinPassword: false,// Does the game require a join password?}asconst;Define user-configurable settings via Zod schema. Settings appear in the UI and are passed to the server container:
import{defineSettings}from"../settings";exportconstyourServerSettings=defineSettings({server_name: {type: "string",label: "Server Name",help: "Displayed in server browser",default: "Your Server",maxLength: 32,},difficulty: {type: "select",label: "Difficulty",default: "normal",options: [{value: "easy",label: "Easy"},{value: "normal",label: "Normal"},{value: "hard",label: "Hard"},],},max_players: {type: "number",label: "Max Players",default: 32,min: 1,max: 128,},enable_pvp: {type: "boolean",label: "Enable PvP",default: true,},});Field types:
string— Text input with optionalmaxLengthnumber— Numeric input with optionalmin/max/stepboolean— Toggle checkboxselect— Dropdown with fixed options
Every field must have a default value.
Define Docker image and build the docker-compose.yml for that game:
importtype{ComposeConfig}from"../compose";importtype{SettingsValues}from"../settings";import{yourServerSettings}from"./settings";exportconstdockerImage="steamcmd/proton:latest";exportconstbuildYourServerCompose=(config: ComposeConfig,settings: SettingsValues<typeofyourServerSettings>): string=>{// Destructure settings passed from userconst{ server_name, difficulty, max_players }=settings;// Build environment variablesconstenv=[`SERVER_NAME=${server_name}`,`DIFFICULTY=${difficulty}`,`MAX_PLAYERS=${max_players}`,];// Return docker-compose YAML as a stringreturn`version: '3'services: yourserver: image: ${dockerImage} container_name: ${config.containerName} environment:${env.map((e)=>` - ${e}`).join("\n")} ports:${config.ports.map((p)=>` - "${p.hostPort}:${p.containerPort}/${p.protocol}"`).join("\n")} volumes: - ${config.serverDir}:/data restart: unless-stopped `.trim();};Once your game is ready, add it to games/index.ts:
import{yourserver}from"./yourserver";exportconstgames=[minecraft,rust,yourserver,// ← Add here// ...];Common sources for game server images:
- Official:
steamcmd/proton:latest,ubuntu:22.04+ manual install - Community: DockerHub (
cm2network/csgo,itzg/minecraft-server) - GameServers.com: Pre-built images for managed games
For Steam-based games, use the steam.ts helpers:
import{buildSteamCmdInstall}from"../steam";exportconstbuildYourServerCompose=(config: ComposeConfig,settings: SettingsValues<typeofyourServerSettings>)=>{conststeamCmd=buildSteamCmdInstall({appId: 232330,// Steam App IDvalidateFiles: true,});return`version: '3'services: yourserver: image: steamcmd/proton:latest container_name: ${config.containerName} environment: - STEAM_APP_ID=232330 command: ${steamCmd} # ... `.trim();};The ports array defines what the player connects to:
ports: [{from: 7777,to: 7777,protocol: "udp"},// Game port{from: 27015,to: 27015,protocol: "udp"},// Query port],Game ports must match what the container expects.
Place game icons at:
public/games/yourserver.png (200×200 PNG, max 50KB)
These are referenced in index.ts via image: "/games/yourserver.png".
bun run typesTest your settings schema:
import{resolveSettings}from"@/games/settings";import{yourServerSettings}from"@/games/yourserver/settings";constsettings=resolveSettings(yourServerSettings,{server_name: "Test",difficulty: "hard",max_players: 64,});Generate and validate the docker-compose output:
# In your app code, call buildCompose with mock ComposeConfig
const compose = buildYourServerCompose(
{
containerName: "test-yourserver",
ports: [{ containerPort: 7777, hostPort: 7777, protocol: "udp" }],
serverDir: "/data",
},
{ server_name: "Test", difficulty: "normal", max_players: 32 }
);
console.log(compose);- Awesome GameServers
- LinuxGSM — Install scripts for many games
- GameServers Dockerfiles
GameDig is used for server status queries. Find supported game protocols:
- GameDig Supported Games
- Common IDs:
minecraft,rust,csgo,valheim,palworld
Here's a minimal but complete recipe template:
games/example/index.ts:
importtype{ComposeConfig}from"../compose";import{resolveSettings}from"../settings";import{buildExampleCompose,dockerImage}from"./install";import{exampleSettings}from"./settings";constbuildCompose=(config: ComposeConfig,raw: unknown): string=>buildExampleCompose(config,resolveSettings(exampleSettings,raw));exportconstexample={
buildCompose,description: "Example server",
dockerImage,enabled: true,gamedigId: "example",id: "example",image: "/games/example.png",name: "Example",ports: [{from: 7777,protocol: "udp",to: 7777}],requirements: {cpu: 1,disk: 5,memory: 2},settings: exampleSettings,usesJoinPassword: false,}asconst;games/example/settings.ts:
import{defineSettings}from"../settings";exportconstexampleSettings=defineSettings({name: {type: "string",label: "Server Name",default: "My Server",},});games/example/install.ts:
importtype{ComposeConfig}from"../compose";importtype{SettingsValues}from"../settings";import{exampleSettings}from"./settings";exportconstdockerImage="example/server:latest";exportconstbuildExampleCompose=(config: ComposeConfig,settings: SettingsValues<typeofexampleSettings>): string=>{return`version: '3'services: example: image: ${dockerImage} container_name: ${config.containerName} environment: - SERVER_NAME=${settings.name} ports: - "${config.ports[0]?.hostPort}:7777/udp" volumes: - ${config.serverDir}:/data restart: unless-stopped `.trim();};Then add to games/index.ts:
import{example}from"./example";exportconstgames=[// ...example,];For implementation help or to discuss recipes:
- Community: https://discord.gg/nodebyte
- Maintainer: oss@nodebyte.co.uk