From f2a0f470019218ff9b8ce157d02d298378c5da27 Mon Sep 17 00:00:00 2001 From: Daniel Gibbs Date: Mon, 18 Aug 2025 18:24:10 +0000 Subject: [PATCH] feat(docker): enhance Dockerfiles and add entrypoint script * Added `gosu` to Dockerfiles for better user switching. * Updated user creation and command execution to run as the `steam` user. * Introduced `docker-entrypoint.sh` to handle UID/GID adjustments at runtime. * Modified README to clarify app installation instructions and data persistence. --- Dockerfile.ubuntu-2004 | 15 +++-- Dockerfile.ubuntu-2204 | 15 +++-- Dockerfile.ubuntu-2404 | 15 +++-- README.md | 141 ++++++++++++++++++++++++----------------- docker-entrypoint.sh | 32 ++++++++++ 5 files changed, 145 insertions(+), 73 deletions(-) create mode 100644 docker-entrypoint.sh diff --git a/Dockerfile.ubuntu-2004 b/Dockerfile.ubuntu-2004 index 04895ee..4935ccf 100644 --- a/Dockerfile.ubuntu-2004 +++ b/Dockerfile.ubuntu-2004 @@ -31,6 +31,7 @@ RUN echo "**** Install SteamCMD ****" \ libsdl2-2.0-0:i386 \ tzdata \ steamcmd \ + gosu \ && ln -s /usr/games/steamcmd /usr/bin/steamcmd \ && locale-gen en_US.UTF-8 \ && apt-get -y autoremove \ @@ -41,16 +42,20 @@ RUN echo "**** Install SteamCMD ****" \ # Add unicode support ENV LANG=en_US.UTF-8 -# Create non-root user/group with configurable IDs +# Create non-root user (default IDs; can be adjusted at runtime by entrypoint) RUN groupadd -g "${PGID}" steam \ && useradd -l -u "${PUID}" -g steam -m -d /home/steam -s /bin/bash steam \ && mkdir -p /home/steam/Steam \ && chown -R steam:steam /home/steam -USER steam + WORKDIR /home/steam -# Bootstrap SteamCMD -RUN steamcmd +login anonymous +quit || true +# Bootstrap SteamCMD as steam user +RUN su -s /bin/bash - steam -c 'steamcmd +login anonymous +quit || true' + +# Copy entrypoint +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh -ENTRYPOINT ["steamcmd"] +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["+help", "+quit"] diff --git a/Dockerfile.ubuntu-2204 b/Dockerfile.ubuntu-2204 index 5efa612..c3486e2 100644 --- a/Dockerfile.ubuntu-2204 +++ b/Dockerfile.ubuntu-2204 @@ -31,6 +31,7 @@ RUN echo "**** Install SteamCMD ****" \ libsdl2-2.0-0:i386 \ tzdata \ steamcmd \ + gosu \ && ln -s /usr/games/steamcmd /usr/bin/steamcmd \ && locale-gen en_US.UTF-8 \ && apt-get -y autoremove \ @@ -41,16 +42,20 @@ RUN echo "**** Install SteamCMD ****" \ # Add unicode support ENV LANG=en_US.UTF-8 -# Create non-root user/group with configurable IDs +# Create non-root user (default IDs; can be adjusted at runtime by entrypoint) RUN groupadd -g "${PGID}" steam \ && useradd -l -u "${PUID}" -g steam -m -d /home/steam -s /bin/bash steam \ && mkdir -p /home/steam/Steam \ && chown -R steam:steam /home/steam -USER steam + WORKDIR /home/steam -# Bootstrap SteamCMD -RUN steamcmd +login anonymous +quit || true +# Bootstrap SteamCMD as steam user +RUN su -s /bin/bash - steam -c 'steamcmd +login anonymous +quit || true' + +# Copy entrypoint +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh -ENTRYPOINT ["steamcmd"] +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["+help", "+quit"] diff --git a/Dockerfile.ubuntu-2404 b/Dockerfile.ubuntu-2404 index 8931540..022094a 100644 --- a/Dockerfile.ubuntu-2404 +++ b/Dockerfile.ubuntu-2404 @@ -34,6 +34,7 @@ RUN echo "**** Install SteamCMD ****" \ libsdl2-2.0-0:i386 \ tzdata \ steamcmd \ + gosu \ && ln -s /usr/games/steamcmd /usr/bin/steamcmd \ && locale-gen en_US.UTF-8 \ && apt-get -y autoremove \ @@ -44,16 +45,20 @@ RUN echo "**** Install SteamCMD ****" \ # Add unicode support ENV LANG=en_US.UTF-8 -# Create non-root user +# Create non-root user (default IDs; can be adjusted at runtime by entrypoint) RUN groupadd -g "${PGID}" steam \ && useradd -l -u "${PUID}" -g steam -m -d /home/steam -s /bin/bash steam \ && mkdir -p /home/steam/Steam \ && chown -R steam:steam /home/steam -USER steam + WORKDIR /home/steam -# Bootstrap SteamCMD -RUN steamcmd +login anonymous +quit || true +# Bootstrap SteamCMD as steam user +RUN su -s /bin/bash - steam -c 'steamcmd +login anonymous +quit || true' + +# Copy entrypoint +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh -ENTRYPOINT ["steamcmd"] +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["+help", "+quit"] diff --git a/README.md b/README.md index 9507f85..1e2616b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ## About -SteamCMD is a command-line version of the Steam client. It allows you to download and install games on a headless server. This container image builds daily and is available on [Docker Hub](https://hub.docker.com/r/gameservermanagers/steamcmd) as well as [GitHub Container Registry](https://github.com/GameServerManagers/docker-steamcmd/pkgs/container/steamcmd). +SteamCMD is a command-line version of the Steam client. It allows you to download and install games/server apps on a headless server. This container image builds daily and is available on [Docker Hub](https://hub.docker.com/r/gameservermanagers/steamcmd) as well as [GitHub Container Registry](https://github.com/GameServerManagers/docker-steamcmd/pkgs/container/steamcmd). ## Tags @@ -32,75 +32,79 @@ Pull the latest image and open SteamCMD interactive prompt: docker run -it --rm gameservermanagers/steamcmd:latest ``` -Download and update a dedicated server into the current host directory (example: Garry's Mod / appid 4020): +Download and update an app into the current host directory (example: Garry's Mod Dedicated Server - appid 4020): ```bash docker run -it --rm \ + -e PUID=$(id -u) -e PGID=$(id -g) \ -v "$PWD:/data" \ gameservermanagers/steamcmd:latest \ - +force_install_dir /data +login anonymous +app_update 4020 validate +quit + +force_install_dir /data +login anonymous +app_update 4020 +quit ``` ## Data Persistence -SteamCMD data (manifests, configs, logs etc) is stored under `/home/steam/.local/share/Steam` (owned by the non‑root `steam` user). Mount a host directory or a docker volume at that path to persist it across runs. Ensure the host path is writable by the container's UID:GID (default `1000:1000`, or your custom `PUID:PGID`). If you dont use `force_install_dir` files will be downloaded to `steamapps/common`. +SteamCMD stores its own library data (manifests, depots, workshop cache) in `/home/steam/.local/share/Steam`. We use `+force_install_dir` to place the app files in its own directory. If you omit `+force_install_dir` (or put it after the first `+app_update`) the app installs under: + +```text +/home/steam/.local/share/Steam/steamapps/common/ +``` + +Using a distinct mount (e.g. `/data`) along with `+force_install_dir /data` keeps app files seperate from the Steam library cache. ### Bind Mount Example ```bash -mkdir -p /path/on/host/steamcmd-data -chown 1000:1000 /path/on/host/steamcmd-data docker run -it --rm \ + -e PUID=$(id -u) -e PGID=$(id -g) \ -v "$PWD:/data" \ - -v /path/on/host/steamcmd-data:/home/steam/.local/share/Steam \ + -v /path/on/host/steamcmd-app:/home/steam/.local/share/Steam \ gameservermanagers/steamcmd:latest \ - +force_install_dir /data +login anonymous +app_update 4020 validate +quit + +force_install_dir /data +login anonymous +app_update 4020 +quit ``` ### Docker Volume Example ```bash docker volume create steamcmd-data +docker volume create steamcmd-app docker run -it --rm \ - -v "$PWD:/data" \ - -v steamcmd-data:/home/steam/.local/share/Steam \ + -v "steamcmd-data:/data" \ + -v steamcmd-app:/home/steam/.local/share/Steam \ gameservermanagers/steamcmd:latest \ - +force_install_dir /data +login anonymous +app_update 4020 validate +quit + +force_install_dir /data +login anonymous +app_update 4020 +quit ``` -## SteamCMD Commands +## User, UID & GID (PUID / PGID) -Common commands / flags you can chain after the image name (order matters; see notes below): +The image uses a non-root user `steam` (default UID:GID 1000:1000). You can override these IDs at runtime with environment variables `PUID` and `PGID`; the entrypoint will map the user/group before executing SteamCMD so created files match your host user. -| Command / Flag | Purpose | Example Snippet | -| ---------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------- | -| `+login anonymous` | Anonymous login (most dedicated servers allow) | `+login anonymous` | -| `+login ` | Authenticated login (needed for some apps / private betas) | `+login myuser mypass` | -| `+@NoPromptForPassword 1` | Suppress interactive password/guard prompts (fail fast) | `+@NoPromptForPassword 1 +login myuser mypass` | -| `+@ShutdownOnFailedCommand 1` | Abort remaining commands if one fails | `+@ShutdownOnFailedCommand 1` | -| `+force_install_dir ` | Set target directory for app files (must come before `+app_update`) | `+force_install_dir /data` | -| `+app_update ` | Install or update an app (server / game) | `+app_update 896660` | -| `+app_update validate` | Integrity check & re-download missing/corrupt files (slower) | `+app_update 740 validate` | -| `+app_status ` | Print install status / progress (debug) | `+app_status 896660` | -| `+app_info_update 1` | Refresh app info cache (used before querying details) | `+app_info_update 1` | -| `+workshop_download_item ` | Download a specific workshop item | `+workshop_download_item 4020 3418671232` | -| `+download_depot ` | Fetch a specific depot/manifest (version pinning) | `+download_depot 90 90 402078904020789` | -| `+sSteamCmdForcePlatformType ` | Force platform (linux / windows / macos) for content | `+sSteamCmdForcePlatformType windows` | -| `+runscript ` | Execute batch of commands from script file | `+runscript /scripts/install.txt` | -| `+quit` | Exit steamcmd when previous commands complete | `+quit` | +### Runtime Override (recommended) -### Command Notes +```bash +docker run --rm \ + -e PUID=1001 -e PGID=1001 \ + -v "$PWD/steam-app:/home/steam/.local/share/Steam" \ + -v "$PWD/server-data:/data" \ + gameservermanagers/steamcmd:latest \ + +force_install_dir /data +login anonymous +app_update 4020 +quit +``` -- Recommended order: `+@ShutdownOnFailedCommand 1 +@NoPromptForPassword 1 +force_install_dir /data +login ... +app_update validate +quit` -- Always place `+force_install_dir` before the first `+app_update` or files go to the default library (`/home/steam/Steam/steamapps`). -- Append `validate` sparingly; use it for initial install or when corruption is suspected. -- Use a script file (`+runscript`) for complex multi-app workflows; each line should mirror the inline form (without shell quoting issues). -- For workshop items, ensure the base app (server) is installed first; workshop content lands under the app's workshop directory. -- `+download_depot` may require authenticated login and correct branch access; manifests are version-specific. +### Build-Time Defaults (optional) -## Notes +You can still bake alternate defaults (not required for most users): -This container is based off of the [steamcmd](https://github.com/steamcmd/docker) container and is primarily used for [LinuxGSM](https://linuxgsm.com) game servers. +```bash +docker build --build-arg PUID=1001 --build-arg PGID=1001 -t steamcmd:uid1001 -f Dockerfile.ubuntu-2404 . +``` + +### Troubleshooting permissions + +| Symptom | Cause | Fix | +| ------------------------------ | --------------------------------------------------- | --------------------------------------------- | +| Files not showing in directory | Host dir owned by different UID | Run with matching PUID/PGID or chown host dir | +| Steam Guard code every run | Sentry file not persisted (no volume or unwritable) | Mount and ensure ownership of Steam data dir | +| Root-owned files in host mount | Ran container with `--user root` | Remove `--user`; use PUID/PGID env vars | ## Login Examples @@ -109,8 +113,12 @@ SteamCMD supports anonymous and authenticated logins. Use anonymous wherever pos ### Anonymous (preferred) ```bash -docker run --rm -it gameservermanagers/steamcmd:latest \ - +force_install_dir /data +login anonymous +app_update 4020 validate +quit +docker run --rm \ + -e PUID=$(id -u) -e PGID=$(id -g) \ + -v "$PWD/steam-app:/home/steam/.local/share/Steam" \ + -v "$PWD/server-data:/data" \ + gameservermanagers/steamcmd:latest \ + +force_install_dir /data +login anonymous +app_update 4020 +quit ``` ### Authenticated (username/password) @@ -123,10 +131,10 @@ This will almost always trigger Steam Guard on first use and again later because ```bash docker run --rm \ + -e PUID=$(id -u) -e PGID=$(id -g) \ -v "$PWD/server-data:/data" \ gameservermanagers/steamcmd:latest \ - +@ShutdownOnFailedCommand 1 \ - +force_install_dir /data \ + +@ShutdownOnFailedCommand 1 +force_install_dir /data \ +login "${STEAM_USER}" "${STEAM_PASS}" +app_update 223350 +quit ``` @@ -136,7 +144,8 @@ Persist `/home/steam/.local/share/Steam` so the sentry (guard) file is cached an ```bash docker run --rm \ - -v "$PWD/steam-data:/home/steam/.local/share/Steam" \ + -e PUID=$(id -u) -e PGID=$(id -g) \ + -v "$PWD/steam-app:/home/steam/.local/share/Steam" \ -v "$PWD/server-data:/data" \ gameservermanagers/steamcmd:latest \ +@ShutdownOnFailedCommand 1 \ @@ -158,17 +167,39 @@ Recommendations: 2. Enter the code when prompted (do NOT add it to scripts). 3. Subsequent scripted runs succeed without further prompts because the sentry file is stored. -If you need fully non‑interactive guarded logins (CI), pre-stage the sentry file by performing step 1 locally and copying the Steam config directory into your CI secret store. +## SteamCMD Commands -## Known Pitfalls +Common commands / flags you can chain after the image name (order matters; see notes below): -- Missing `+force_install_dir` before `+app_update` causes files to land in the default Steam library instead of your intended mount. -- Running as root (by overriding user) can create permission mismatches when later running as the default `steam` user. -- Using `validate` every run significantly slows updates; reserve it for first install or suspected corruption. -- Authenticated logins without a persistent Steam data volume will continually re-trigger Steam Guard challenges. -- Workshop items for some games require the game server to read them from a specific path; double‑check game docs for any required symlinks or copy steps. -- Clock skew or wrong system time (TZ/ntp) can cause auth or SSL errors; ensure the host clock is correct. -- Over-zealous bind mounts (e.g. mounting an empty host dir over `/home/steam`) can hide required directories created during image build. +| Command / Flag | Purpose | Example Snippet | +| ---------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------- | +| `+login anonymous` | Anonymous login (most dedicated servers allow) | `+login anonymous` | +| `+login ` | Authenticated login (needed for some apps / private betas) | `+login myuser mypass` | +| `+@NoPromptForPassword 1` | Suppress interactive password/guard prompts (fail fast) | `+@NoPromptForPassword 1 +login myuser mypass` | +| `+@ShutdownOnFailedCommand 1` | Abort remaining commands if one fails | `+@ShutdownOnFailedCommand 1` | +| `+force_install_dir ` | Set target directory for app files (must come before `+app_update`) | `+force_install_dir /data` | +| `+app_update ` | Install or update an app (server / game) | `+app_update 896660` | +| `+app_update validate` | Integrity check & re-download missing/corrupt files (slower) | `+app_update 740 validate` | +| `+app_status ` | Print install status / progress (debug) | `+app_status 896660` | +| `+app_info_update 1` | Refresh app info cache (used before querying details) | `+app_info_update 1` | +| `+workshop_download_item ` | Download a specific workshop item | `+workshop_download_item 4020 3418671232` | +| `+download_depot ` | Fetch a specific depot/manifest (version pinning) | `+download_depot 90 90 402078904020789` | +| `+sSteamCmdForcePlatformType ` | Force platform (linux / windows / macos) for content | `+sSteamCmdForcePlatformType windows` | +| `+runscript ` | Execute batch of commands from script file | `+runscript /scripts/install.txt` | +| `+quit` | Exit steamcmd when previous commands complete | `+quit` | + +### Command Notes + +- Recommended order: `+@ShutdownOnFailedCommand 1 +@NoPromptForPassword 1 +force_install_dir /data +login ... +app_update validate +quit` +- Always place `+force_install_dir` before the first `+app_update` or files go to the default library (`/home/steam/Steam/steamapps`). +- Append `validate` sparingly; use it for initial install or when corruption is suspected. +- Use a script file (`+runscript`) for complex multi-app workflows; each line should mirror the inline form (without shell quoting issues). +- For workshop items, ensure the base app (server) is installed first; workshop content lands under the app's workshop directory. +- `+download_depot` may require authenticated login and correct branch access; manifests are version-specific. + +## Notes + +This container is based off of the [steamcmd](https://github.com/steamcmd/docker) container and is primarily used for [LinuxGSM](https://linuxgsm.com) game servers. ## FAQ @@ -181,11 +212,5 @@ Usually transient network or firewall DPI interference. Retry, or ensure outboun **Q: Can I run multiple updates in one container invocation?** Yes, either chain multiple `+app_update` commands or use a runscript file with one per line. -**Q: How do I pin a specific version?** -Use `+download_depot` with a known manifest id (from SteamDB) after installing the main app. Keep in mind manifests disappear over time. - -**Q: My login works locally but not in CI. Why?** -Your sentry file (Steam Guard token) wasn't copied. Persist and supply the Steam config directory or use anonymous where allowed. - **Q: Do I need to expose any ports?** No for downloading content. Game server ports are handled by the separate server container you build/run using the downloaded files. diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..30a78fe --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Allow runtime override of steam user UID/GID via PUID/PGID env vars. +# If they differ from current numeric IDs, we modify /etc/passwd & /etc/group accordingly +# before executing steamcmd. This requires the container to start as root. + +CURRENT_UID="$(id -u steam)" +CURRENT_GID="$(id -g steam)" +DESIRED_UID="${PUID:-$CURRENT_UID}" +DESIRED_GID="${PGID:-$CURRENT_GID}" + +if [[ "${DESIRED_GID}" != "${CURRENT_GID}" ]]; then + echo "Updating steam group GID: ${CURRENT_GID} -> ${DESIRED_GID}" >&2 + groupmod -o -g "${DESIRED_GID}" steam + # Fix any files owned by old GID inside home (best effort) + find /home/steam -xdev -group "${CURRENT_GID}" -exec chgrp -h "${DESIRED_GID}" {} + || true +fi + +if [[ "${DESIRED_UID}" != "${CURRENT_UID}" ]]; then + echo "Updating steam user UID: ${CURRENT_UID} -> ${DESIRED_UID}" >&2 + usermod -o -u "${DESIRED_UID}" steam + find /home/steam -xdev -user "${CURRENT_UID}" -exec chown -h "${DESIRED_UID}" {} + || true +fi + +# Ensure ownership of Steam data root (non-recursive check first, then minimal fix if needed) +if [[ ! -w /home/steam ]]; then + echo "Warning: /home/steam not writable after UID/GID adjustment" >&2 +fi + +# Drop to steam user and run steamcmd +exec gosu steam:steam steamcmd "$@"