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 image is rebuilt weekly (and whenever the Dockerfile changes) so the Ubuntu base and SteamCMD stay current. It is published to Docker Hub and GitHub Container Registry:
gameservermanagers/steamcmd
ghcr.io/gameservermanagers/steamcmd
Images are built for linux/amd64 only. SteamCMD is an x86 binary and does not run natively on ARM.
| Tag(s) | Ubuntu Release | Standard Support Ends* | Notes |
|---|---|---|---|
latest, ubuntu |
26.04 LTS (Resolute Raccoon) | May 2031 | Current LTS |
ubuntu-26.04 |
26.04 LTS (Resolute Raccoon) | May 2031 | Current LTS |
ubuntu-24.04 |
24.04 LTS (Noble Numbat) | May 2029 | Previous LTS |
ubuntu-22.04 |
22.04 LTS (Jammy Jellyfish) | June 2027 | Older LTS |
ubuntu-20.04 |
20.04 LTS (Focal Fossa) | May 2025 (ended) | Legacy (ESM only; may be removed) |
*Dates are the end of standard (free) security updates according to Canonical's LTS schedule. Extended Security Maintenance (ESM) may continue past these dates. An image may be deprecated sooner if upstream packages (such as SteamCMD dependencies) become unavailable.
Pin a specific ubuntu-XX.04 tag if you need a stable base. latest moves to each new Ubuntu LTS shortly after release.
Pull the latest image and open SteamCMD interactive prompt:
docker run -it --rm gameservermanagers/steamcmd:latestDownload and update an app into the current host directory (example: Garry's Mod Dedicated Server - appid 4020):
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 +quitservices:
steamcmd:
image: gameservermanagers/steamcmd:latest
environment:
PUID: 1000
PGID: 1000
volumes:
- ./server-data:/data
- ./steam-app:/home/steam/.local/share/Steam
command: +force_install_dir /data +login anonymous +app_update 4020 validate +quitdocker compose run --rm steamcmdSteamCMD stores its own files (the SteamCMD binaries, app manifests, depot cache, workshop cache and login data) in /home/steam/.local/share/Steam. Use +force_install_dir to put the app files in their own directory. If you leave out +force_install_dir, or put it after the first +app_update, the app is installed to:
/home/steam/.local/share/Steam/steamapps/common/<AppName>
Mount a separate directory (e.g. /data) and pass +force_install_dir /data to keep the app files apart from the Steam cache. Also persisting /home/steam/.local/share/Steam means SteamCMD doesn't re-download its own update on every run, and keeps your login cached.
docker run -it --rm \
-e PUID=$(id -u) -e PGID=$(id -g) \
-v "$PWD:/data" \
-v "/path/on/host/steamcmd-app:/home/steam/.local/share/Steam" \
gameservermanagers/steamcmd:latest \
+force_install_dir /data +login anonymous +app_update 4020 +quitdocker volume create steamcmd-data
docker volume create steamcmd-app
docker run -it --rm \
-v "steamcmd-data:/data" \
-v "steamcmd-app:/home/steam/.local/share/Steam" \
gameservermanagers/steamcmd:latest \
+force_install_dir /data +login anonymous +app_update 4020 +quitThe image uses a non-root user steam (default UID:GID 1000:1000). To change these IDs at runtime, set the PUID and PGID environment variables. The entrypoint starts as root, remaps the steam user/group, then drops privileges with gosu before running SteamCMD, so the files it creates match your host user.
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 +quitIf your platform requires the container to start as non-root (e.g. --user 1000:1000, rootless setups, or Kubernetes runAsUser), the entrypoint skips the remapping and runs SteamCMD as that user. PUID/PGID are ignored in this mode. The built-in Steam directory is owned by UID 1000, so with any other UID you must mount a writable directory at /home/steam/.local/share/Steam.
There is a single Dockerfile; choose the Ubuntu release with the UBUNTU_VERSION build argument (defaults to the current LTS). You can also bake in alternate default IDs:
docker build --build-arg UBUNTU_VERSION=24.04 --build-arg PUID=1001 --build-arg PGID=1001 -t steamcmd:ubuntu-24.04-uid1001 .| 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 | Steam data dir not persisted (no volume or unwritable) | Mount and ensure ownership of the Steam data dir |
| Root-owned files in host mount | Ran container with --user root |
Remove --user; use PUID/PGID env vars |
Permission denied on startup |
Started with --user other than 1000 without a Steam data dir |
Use PUID/PGID instead, or mount a writable Steam dir too |
SteamCMD supports anonymous and authenticated logins. Use anonymous wherever possible.
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 +quitSome apps (e.g. certain private branches, tools, or beta depots) require a Steam account.
Because nothing is persisted, this will almost always ask for a Steam Guard code on first use, and again on later runs:
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 \
+login "${STEAM_USER}" "${STEAM_PASS}" +app_update 223350 +quitPersist /home/steam/.local/share/Steam so the login is cached and later runs skip the Steam Guard prompt.
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 \
+@ShutdownOnFailedCommand 1 \
+force_install_dir /data \
+login "${STEAM_USER}" "${STEAM_PASS}" +app_update 223350 +quitRecommendations:
- For authenticated workflows, always mount persistent Steam data (see Data Persistence above) to avoid repeated Steam Guard prompts.
- Pass credentials via environment variables or a one-shot secret. Don't hard-code them in compose files committed to source control.
- When Steam Guard (2FA / email) asks for a code, you can pass it inline as a third argument (
+login user pass CODE). Or run once interactively to cache the login in the mounted Steam directory. - Use a dedicated account that only has the entitlements you need, never your main account.
- Treat the mounted Steam data directory as sensitive because it contains cached login tokens. Do not publish it.
- Run interactively (
docker run -it ...) once with a persistent Steam data mount. - Enter the code when prompted (do NOT add it to scripts).
- Later scripted runs succeed without prompting because the login is cached.
Common commands/flags you can chain after the image name (order matters; see the notes below):
| 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 |
+@sSteamCmdForcePlatformType <os> |
Force platform (linux / windows / macos) for content | +@sSteamCmdForcePlatformType windows |
+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> -beta <branch> |
Install a specific beta branch | +app_update 896660 -beta public-test |
+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 |
+runscript <file> |
Execute batch of commands from script file | +runscript /scripts/install.txt |
+quit |
Exit steamcmd when previous commands complete | +quit |
- Recommended order:
+@ShutdownOnFailedCommand 1 +@NoPromptForPassword 1 +force_install_dir /data +login ... +app_update <appid> validate +quit - Always put
+force_install_dirbefore+loginand the first+app_update. Otherwise files go to the default library (/home/steam/.local/share/Steam/steamapps/common). @settings such as+@sSteamCmdForcePlatformTypemust also come before+login.- Only append
validatewhen you need it: for an initial install, or when you suspect corruption. - Use a script file (
+runscript) for complex multi-app workflows. Write each line like the inline form, without the leading+and without shell quoting. - For workshop items, install the base app (server) first. Workshop content lands in the app's workshop directory.
+download_depotignores+force_install_dirand saves to/home/steam/.local/share/Steam/steamcmd/linux32/steamapps/content/. It may need an authenticated login with access to the right branch. Manifest IDs are version-specific.
Q: How do I find an appid? Search SteamDB and use the numeric App ID shown.
Q: Why do I get "Failed to connect" or SSL errors? Usually this is a transient network problem, or a firewall doing deep packet inspection. Retry, or check that outbound HTTPS (443) and the Steam ports (TCP/UDP 27015–27050) are open.
Q: Can I run multiple updates in one container invocation?
Yes. Either chain several +app_update commands, or use a runscript file with one per line.
Q: Do I need to expose any ports? Not for downloading content. Game server ports belong to the separate server container you build/run from the downloaded files.
Q: Does it run on ARM (Raspberry Pi, Apple Silicon)?
Not natively. SteamCMD is x86-only. On Apple Silicon, Docker Desktop can run the amd64 image under emulation (--platform linux/amd64), but it will be slower.
This image is inspired by the steamcmd/docker image and is primarily used for LinuxGSM game servers.
The versions that get built are listed in ubuntu-versions.json. A weekly workflow checks Ubuntu's release data for a new LTS. When one is released and the image builds and passes a SteamCMD smoke test, the workflow opens a pull request that adds it and moves latest to it.
