Skip to content

Steam Deployment

Summary: Builds are uploaded to Steam via steamcmd (SteamPipe) using a generated app_build VDF. Dev and release builds upload automatically and go live on the non-default development Steam branch; a standalone workflow allows manual uploads of existing builds. Login uses credentials cached on the runner — no password lives in CI.

Table of Contents


Architecture Overview

┌─────────────────────┐       ┌──────────────────────┐       ┌──────────────┐
│  Packaged Build     │       │  steamcmd             │       │  Steamworks  │
│  C:\BuildArtifacts\ │──────▶│  run_app_build        │──────▶│  (SteamPipe) │
│  <run_number>\      │       │  (generated VDF)      │       │              │
└─────────────────────┘       └──────────────────────┘       └──────────────┘
                                       │
                                       │ cached credentials (config.vdf on runner)
                                       ▼
                              ┌──────────────────────┐
                              │  GitHub Secrets       │
                              │  STEAM_USERNAME       │
                              └──────────────────────┘
  • steamcmd: C:\Tools\Steamworks\sdk\tools\ContentBuilder\builder\steamcmd.exe (Steamworks SDK 1.65, self-updates on run)
  • Build output/cache: C:\SteamBuildOutput (chunk cache — keep it, it speeds up delta uploads)
  • The app_build VDF is generated per run into $RUNNER_TEMP because ContentRoot and Desc change every build. There is no committed VDF.

App / Depot IDs

Item Value
App ID 5091420
Windows depot 5091421 (Steamworks default depot, appid+1)
Depot mapping * recursive from the packaged build root (same dir the EGS upload uses)

Authentication (One-Time Runner Setup)

CI uses a dedicated builder account (username in the STEAM_USERNAME GitHub secret; Steam account registered to builder@eternal-server.net, a Cloudflare Email Routing forward). Steam Guard blocks unattended password logins, so the account must log in interactively once on the runner machine, as the same Windows user the runner runs as:

& C:\Tools\Steamworks\sdk\tools\ContentBuilder\builder\steamcmd.exe +login <builder_username>
# enter password + Steam Guard code when prompted, then `quit`

This caches a login token in the builder's config/config.vdf; subsequent +login <username> (no password) works unattended. The token survives reboots but is invalidated if the account's password changes or the token expires (rare) — the fix is always to re-run the interactive login.

Builder account requirements in Steamworks (Users & Permissions): - Edit App Metadata + Publish App Changes on app 5091420. Nothing else.


Upload Flow

Automated (via build-and-package.yml, upload-steam job)

Build Completes
      │
      ▼
Upload Job Runs? ──No──▶ (main branch: skip)
      │
     Yes (dev, release/*, or upload_steam input)
      │
      ▼
Generate app_build VDF (AppID 5091420, depot 5091421, SetLive development)
      │
      ▼
steamcmd +login (cached) +run_app_build ──▶ chunks, uploads, sets live on 'development'
      │
      ▼
Discord Notification

Runs in parallel with the upload-egs job on the same triggers.

Version Format

Same as EGS: YYYY.MM.DD-<run>-<sha> automated, manual-<build_number>-<run> standalone. The version string goes into the build's Desc, visible in Steamworks build history.


Steam Branches

  • SetLive "development" in the VDF makes each upload immediately live on the development branch — created in App Admin > SteamPipe > Builds ("Create new app branch"; can be password-protected). Opt into it in the Steam client via Properties > Betas.
  • Branch names need at least 4 characters (a branch literally named dev is rejected) and no spaces.
  • The default branch can NOT be set live from steamcmd. Promoting a build to default is always a manual click in Steamworks > SteamPipe > Builds ("Set build live for branch..." dropdown > Preview Change > Set Build Live Now).
  • The branch UI only appears usable once at least one build has been uploaded — the very first upload must go up with SetLive empty (upload-only), then the branch is created and set live by hand.
  • Unlike EGS labels, Steam builds don't auto-delete; old builds stay in build history.

Standalone Upload Workflow

For uploading existing builds without rebuilding — GitHub Actions > "Upload to Steam (standalone)" > Run workflow:

Input Description Default
build_number Folder name in C:\BuildArtifacts\ Required
steam_branch Branch to set live (empty = upload only) development

Troubleshooting

Error Cause Fix
FAILED (Invalid Password) / login hangs waiting for Guard code Cached token missing/expired, or job runs as a different Windows user than the one that cached it Re-run the one-time interactive login on the runner
Failed to commit build / App state is invalid First-time SteamPipe setup incomplete In Steamworks: create the depot, add it to the default launch config, publish once
Trying to set live branch 'default' error steamcmd cannot set default live Use a non-default branch, promote manually
Set-live fails naming a missing branch Branch doesn't exist (or was renamed) Create it in SteamPipe > Builds; branch names: ≥4 chars, no spaces
Upload succeeds but build not visible in client Branch not opted into Steam client > game Properties > Betas > select development
Slow full re-upload every time C:\SteamBuildOutput was deleted Leave the cache directory in place


Source References

File Purpose
.github/workflows/build-and-package.yml Automated upload after build (upload-steam job)
.github/workflows/upload-steam.yml Standalone manual upload workflow

Recent Changes

Date Change Impact
2026-08-11 Branch renamed to development (Steam requires ≥4-char branch names) CI sets live on development
2026-08-10 Initial Steam deployment (SDK 1.65, steamcmd, development-branch auto-set-live) Steam uploads mirror the EGS flow