Imported from JamesIves/hlds-docker (
AGENTS.md). Install upstream withnpx skills add JamesIves/hlds-docker. Copyright stays with the author.
Agents
[!NOTE] This document is intended for AI agents and tools such as GitHub Copilot. If you're a human, check out the Getting Started guide or the Contributing guide instead.
Repository Overview 📖
This repository provides a Dockerised solution for running the Half-Life Dedicated Server (HLDS), supporting all classic GoldSrc games and mods. The project uses Docker to simplify server setup, with support for custom configurations, plugins, and mods. Pre-built images are published to Docker Hub and GitHub Container Registry via GitHub Actions CI/CD pipelines.
Technologies 🔧
- Docker: Containerises the HLDS server. The
Dockerfilelives incontainer/and supports build arguments (GAME,FLAG,VERSION,IMAGE). - Docker Compose: Two compose files:
docker-compose.yml(root, for end-users pulling pre-built images) andcontainer/docker-compose.yml(for building custom images locally). - GitHub Actions: CI/CD workflows in
.github/workflows/for validation, beta publishing, production publishing, sponsor management, and PR labeling. - Shell Scripting:
container/entrypoint.shhandles runtime initialisation (mod syncing, config syncing, server startup). - SteamCMD: Downloads HLDS game files during the Docker build via the
container/hlds.txtscript.
Project Structure 📂
├── AGENTS.md # This file
├── ARCHITECTURE.md # Architecture documentation with diagrams
├── MAINTENANCE.md # Health checks, log forwarding, RCON secrets
├── .github/
│ ├── workflows/
│ │ ├── validate.yml # CI: builds and validates all 12 game variants
│ │ ├── publish.yml # CD: version bump → build → test → push to registries → GitHub release
│ │ ├── beta.yml # CD: builds and pushes beta-tagged images
│ │ ├── sponsors.yml # Updates README with GitHub Sponsors
│ │ └── label.yml # Auto-labels PRs via conventional commits
│ ├── ISSUE_TEMPLATE/ # Bug report form and config
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── CODEOWNERS # @JamesIves owns all files
│ ├── dependabot.yml # Weekly updates for Actions and Docker
│ ├── release.yml # Changelog categories for releases
│ └── FUNDING.yml # GitHub Sponsors
├── container/ # Docker build context
│ ├── Dockerfile # Ubuntu base, SteamCMD, HLDS
│ ├── entrypoint.sh # Runtime: validates args, optional AUTO_UPDATE, syncs mods/config, starts hlds_run
│ ├── healthcheck.sh # Docker HEALTHCHECK: raw A2S_INFO query over UDP via /dev/udp
│ ├── hlds.txt # SteamCMD install script (app 90, runs 3x for reliability)
│ ├── docker-compose.yml # For building custom images locally
│ ├── config/ # Default configs baked into the image
│ │ ├── server.cfg # Default hostname and contact
│ │ ├── autoexec.cfg # Executes default.cfg
│ │ ├── default.cfg # Empty placeholder for user customisation
│ │ └── motd.txt # HTML message of the day
│ └── mods/ # Empty by default; mods baked into custom builds go here
├── config/ # User-provided configs (volume-mounted at runtime, gitignored)
├── mods/ # User-provided mods (volume-mounted at runtime, gitignored)
├── docker-compose.yml # End-user compose file pulling pre-built images
├── docs/
│ └── index.html # Retro-styled web UI for generating Docker commands
├── README.md
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
└── LICENSE # MIT
Supported Games 🎮
All images use SteamCMD app ID 90 with a mod config to select the game variant:
| Game Identifier | Game Name | Legacy Available |
|---|---|---|
valve |
Half-Life Deathmatch | Yes |
cstrike |
Counter-Strike | Yes |
czero |
Counter-Strike: Condition Zero | Yes |
dmc |
Deathmatch Classic | No |
gearbox |
Half-Life: Opposing Force | No |
ricochet |
Ricochet | No |
dod |
Day of Defeat | No |
tfc |
Team Fortress Classic | Yes |
Legacy variants use the -beta steam_legacy flag to install the pre-25th Anniversary Edition of the game.
Key Build Arguments 🏗️
| Argument | Purpose | Default |
|---|---|---|
GAME |
GoldSrc game/mod identifier passed to SteamCMD | valve |
FLAG |
Additional SteamCMD flags (e.g., -beta steam_legacy) |
(empty) |
VERSION |
Semantic version tag, set by CI | custom |
IMAGE |
Full image name with tag, used in the entrypoint startup banner | custom |
Runtime Volume Mounts 💾
| Host Path | Container Path | Purpose |
|---|---|---|
./config |
/temp/config |
Config files synced into /opt/steam/hlds/$GAME/ on container start |
./mods |
/temp/mods |
Mod directories synced into /opt/steam/hlds/ on container start |
Runtime Environment Variables 🔧
| Variable | Purpose | Default |
|---|---|---|
AUTO_UPDATE |
If 1/true, re-runs SteamCMD on every container start to refresh game files |
(unset) |
RCON_PASSWORD_FILE |
Path to a mounted secret file; its contents become +rcon_password, avoiding a plaintext CLI arg |
(unset) |
PORT |
Port healthcheck.sh queries with A2S_INFO; set this if +port was changed |
27015 |
Network Ports 🌐
| Port | Protocol | Purpose |
|---|---|---|
27015 |
TCP/UDP | Game server traffic and RCON |
26900 |
UDP | Steam master server |
Entrypoint Behaviour 🚪
- Warns if no
+mapargument is found in the startup command. - Warns if
+rcon_passwordis still set to the doc/example defaultchangeme. - If
RCON_PASSWORD_FILEis set and exists, reads the password from that file and appends+rcon_password(a user-supplied+rcon_passwordin the command still wins, since it's applied last). - If
AUTO_UPDATEis set, re-runs SteamCMD against the persisted install directory before syncing mods/config, so a restart alone can pick up a new Valve patch. - Syncs files from
/temp/mods→/opt/steam/hlds/usingrsync. - Syncs files from
/temp/config→/opt/steam/hlds/$GAME/usingrsync. - Prints a branded startup banner with image, version, and game info.
- Launches
hlds_runwith the specified game and all passed arguments.
Independently of the entrypoint, Docker polls healthcheck.sh on a fixed schedule (30s interval, 5s timeout, 60s start period, 3 retries) to report container health via HEALTHCHECK.
CI/CD Workflows 🔄
validate.yml: Validation
- Trigger: Push to any branch except
mainandbeta, or manual dispatch. - Matrix: All 12 game variants (8 games + 4 legacy).
- Steps: Build image → create test config/mod files → run container → validate directory mappings and game data → cleanup.
beta.yml: Beta Publishing
- Trigger: Push to
betabranch. - Matrix: All 12 game variants.
- Steps: Build → validate → push to Docker Hub (
jives/hlds:<game>-beta) and GHCR (ghcr.io/jamesives/hlds:<game>-beta).
publish.yml: Production Publishing
- Trigger: Manual dispatch (
workflow_dispatch) frombetawith abumpchoice (patch/minor/major), or a weeklyschedulethat rebuildsmainas-is against the current Steam depot. - Jobs:
check-for-updates(schedule only): queries Steam'sapp_infoand stops the run if nothing changed since the last release.version: computes the next semantic version from the last GitHub Release.prepare(dispatch only): mergesbetainto a scratchrelease-candidateref.test: builds and validates all 12 game variants (Trivy scan + smoke test) against the candidate (dispatch) ormain(schedule).merge(dispatch only): fast-forwardsmainto the tested candidate.release: builds and pushes all 12 variants to Docker Hub and GHCR with both<game>and<game>-<version>tags, and attests provenance.publish: creates the GitHub Release and version tag.
sponsors.yml: Sponsor Management
- Trigger: Daily cron + manual dispatch.
- Steps: Generates sponsor avatars in
README.md, deploys tobetabranch.
label.yml: PR Labeling
- Trigger: Pull request events.
- Steps: Auto-assigns labels based on conventional commit prefixes in PR titles.
Contribution Flow 🤝
- Issues/discussions are filed on GitHub.
- Contributors branch from
beta. - Push triggers
validate.ymlfor automated testing. - PRs merge into
beta→ triggersbeta.yml→ publishes-betatagged images. - Maintainer manually triggers
publish.ymlviaworkflow_dispatchwith abumpchoice → mergesbetaintomain→ builds and validates all variants → pushes production images → creates a GitHub Release. The same workflow also runs weekly on its own to rebuildmainagainst the current Steam depot when Valve ships an update, without promoting any new code.
Coding Conventions 📏
- Code comments and documentation (READMEs,
AGENTS.md,ARCHITECTURE.md,CODE_OF_CONDUCT.md, etc.) must use British English spelling (e.g.,colour,behaviour,licenceas a noun,synchronise). This doesn't apply to identifiers, fixed spec fields, or external API names that are spelled in American English (e.g., the OCIorg.opencontainers.image.licenseslabel, or thesynchronizepull request event type) - those must stay as-is to remain valid. - The
Dockerfileruns as a non-rootsteamuser for security. - SteamCMD
app_updateruns 3 times inhlds.txtfor download reliability. - Config files use
rsyncfor syncing to preserve directory structure and handle overwrites. - All OCI labels are applied to images for discoverability.
- Legacy game variants strip the
-legacysuffix before passing to SteamCMD, using theFLAGvariable to select the beta branch instead. - The
container/config/directory contains defaults baked into every image; the rootconfig/directory is for user overrides at runtime and is gitignored. - The
container/mods/directory is for mods baked into custom builds; the rootmods/directory is for user mods at runtime and is gitignored.
Architecture Maintenance 🏛️
ARCHITECTURE.md should be kept up to date with any major architectural changes. When modifying the build process, entrypoint behaviour, CI/CD pipeline, volume mapping strategy, or container file system layout, update the corresponding diagrams and descriptions in ARCHITECTURE.md. During code reviews, reviewers should check that ARCHITECTURE.md still accurately reflects the current state of the project.
AGENTS.md should also be kept up to date when major changes are made. If workflow triggers, supported games, build arguments, volume mounts, ports, entrypoint behaviour, or project structure change, update the corresponding sections in this file.