CI/CD for game builds: how i stopped shipping broken exports
after the third time i shipped a build to playtesters with a missing asset that i swore was fine on my machine, i finally sat down and wired up a proper CI pipeline for my Godot project. took a weekend but it's saved me probably a dozen embarrassing re-exports since then, so here's how it works.
i'm using GitHub Actions with the official Godot export templates. the core of it is pretty simple — on every push to main or when i tag a release, the workflow checks out the repo, pulls the Godot headless binary, installs export templates, and runs the export. the resulting builds get uploaded as artifacts, and tagged releases automatically push to itch.io via butler.
name: Export Godot Project
on:
push:
branches: [main]
release:
types: [published]
jobs:
export-windows:
runs-on: ubuntu-latest
container:
image: barichello/godot-ci:4.3
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Setup export templates
run: |
mkdir -p ~/.local/share/godot/export_templates/4.3.stable
mv /root/.local/share/godot/export_templates/4.3.stable ~/.local/share/godot/export_templates/4.3.stable
- name: Export Windows build
run: |
mkdir -p build/windows
godot --headless --export-release "Windows Desktop" build/windows/mygame.exe
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: windows-build
path: build/windows/the barichello/godot-ci docker image does most of the heavy lifting — it has the headless editor and templates pre-installed, so you're not spending 3 minutes of CI time downloading a 300MB binary every run. for the itch.io push step on tags i just add a butler step after the export:
- name: Push to itch.io
if: github.event_name == 'release'
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
run: |
curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
unzip butler.zip
./butler push build/windows yourusername/mygame:windows --userversion ${{ github.ref_name }}a few things that tripped me up: GIT LFS. if you're storing any assets in LFS and you forget lfs: true on the checkout action, you'll get a fully working build that's missing half its textures. also make sure your .export_presets.cfg is committed — Godot won't export without it and it's weirdly easy to add it to .gitignore by mistake.
the other big win was adding a simple GDScript lint step using godot --headless --check-only -s some_script.gd on changed files. it doesn't catch logic errors obviously but it does catch syntax errors and missing class references before a build even starts.
i'm running this on a free GitHub Actions tier and most exports finish in under 4 minutes. for a solo project that's more than fast enough. curious whether anyone has this set up for Unity — i've heard the licensing situation with Unity's CI stuff is its own whole headache.