← Dev Log
BallOrcs1 min read

Our own forge: GitLab, a registry, and runners in a 10 GB box

We built our own private build factory: a code-hosting site, an image warehouse and robot workers, all running on one modest Windows PC.

In plain words

We stood up our own private copy of the kind of service big studios rent: a code-hosting site, an image warehouse for our build machines, and robot workers that rebuild the game on every change. All of it runs inside one modest Windows PC. The robots' first day was rough — every job needs the right tools in reach, and a cold engine build takes longer than the default "give up" timer — but by the end the assembly line ran unattended.

Under the hood

  • GitLab CE 19.1.2 + container registry via pinned docker-compose in WSL, omnibus memory-tuned to coexist with builds in ~10 GB. up.sh is idempotent across version bumps; bootstrap.sh mints a short-lived root PAT via gitlab-rails and creates the helixplay group and projects.
  • Toolchain images: Godot's official build-containers, built at their pinned commit and pushed to our registry, digest-pinned in versions.lock. CI jobs run in a thin ci-shell image and launch toolchain containers as siblings via the runner's docker socket.
  • check_pins got smarter: each build fails closed on exactly the pins it uses — so the deliberately unpinned XCODE_VERSION (no Mac hardware yet) doesn't block Linux/Windows/Web/Android CI.
  • Reality-driven tuning: build jobs get a 4-hour timeout because a cold engine build exceeds the 1-hour default, and a persistent scons object cache is mounted into containers (CacheDir keys hash command line + inputs, so determinism is unaffected) — retries dropped from ~83 minutes of engine rebuild to minutes.

The runner registers via API, clones over the internal compose network, and binds /builds to the host so sibling-container mounts resolve. None of this is glamorous; all of it is the difference between "CI exists" and "CI is trusted."

#infrastructure#ci