Browse Source

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.
pull/82/head
Daniel Gibbs 10 months ago
parent
commit
f2a0f47001
Failed to extract signature
  1. 15
      Dockerfile.ubuntu-2004
  2. 15
      Dockerfile.ubuntu-2204
  3. 15
      Dockerfile.ubuntu-2404
  4. 141
      README.md
  5. 32
      docker-entrypoint.sh

15
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"]

15
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"]

15
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"]

141
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/<AppName>
```
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 <user> <pass>` | 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 <path>` | Set target directory for app files (must come before `+app_update`) | `+force_install_dir /data` |
| `+app_update <appid>` | Install or update an app (server / game) | `+app_update 896660` |
| `+app_update <appid> validate` | Integrity check & re-download missing/corrupt files (slower) | `+app_update 740 validate` |
| `+app_status <appid>` | 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 <appid> <itemid>` | Download a specific workshop item | `+workshop_download_item 4020 3418671232` |
| `+download_depot <appid> <depotid> <manifest>` | Fetch a specific depot/manifest (version pinning) | `+download_depot 90 90 402078904020789` |
| `+sSteamCmdForcePlatformType <os>` | Force platform (linux / windows / macos) for content | `+sSteamCmdForcePlatformType windows` |
| `+runscript <file>` | 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 <appid> 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 <user> <pass>` | 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 <path>` | Set target directory for app files (must come before `+app_update`) | `+force_install_dir /data` |
| `+app_update <appid>` | Install or update an app (server / game) | `+app_update 896660` |
| `+app_update <appid> validate` | Integrity check & re-download missing/corrupt files (slower) | `+app_update 740 validate` |
| `+app_status <appid>` | 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 <appid> <itemid>` | Download a specific workshop item | `+workshop_download_item 4020 3418671232` |
| `+download_depot <appid> <depotid> <manifest>` | Fetch a specific depot/manifest (version pinning) | `+download_depot 90 90 402078904020789` |
| `+sSteamCmdForcePlatformType <os>` | Force platform (linux / windows / macos) for content | `+sSteamCmdForcePlatformType windows` |
| `+runscript <file>` | 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 <appid> 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.

32
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 "$@"
Loading…
Cancel
Save