Unreal Engine CI with GitHub Actions and a self-hosted runner
Build an Unreal Engine project on every push with GitHub Actions: set up a self-hosted Windows runner, a workflow that packages with BuildCookRun, and the settings that keep builds fast.
If your project is on GitHub, Actions is the natural place to build it. There's one catch with Unreal: GitHub's standard hosted runners don't come with the engine and don't have the disk space an Unreal build needs. So Unreal teams run a self-hosted runner: their own machine, with the engine installed, that picks up jobs from GitHub.
This guide sets up a Windows runner and a workflow that packages your game on every push.
Private repositories only
GitHub recommends self-hosted runners only for private repositories. On a public one, anyone can open a pull request from a fork that runs their code on your machine.
Prepare the machine
The runner builds exactly like you would, so the machine needs the same setup as a developer's PC:
- Windows with plenty of free disk space. The engine, your project, its
Intermediatefolder, the derived data cache and the packaged build add up fast. - Unreal Engine, the same version as your project.
- Visual Studio 2022 with the Game development with C++ workload and a Windows SDK. See Unable to find valid toolchain for the exact components.
- Git and Git LFS, and long paths turned on, since Unreal projects nest folders deeply:
git config --system core.longpaths trueBefore going further, package the project once by hand on that machine with BuildCookRun. If it works there, it works in Actions.
Add the runner
In your repository on GitHub, open Settings → Actions → Runners and click New self-hosted runner. Pick Windows and x64.
On the build machine, open PowerShell as administrator and run the download and config.cmd commands GitHub shows, in order. GitHub suggests C:\actions-runner as the folder. The token in the commands expires after an hour, so run them right away.
When config.cmd asks whether to run the runner as a service, say yes. It then starts with Windows and keeps running when you log out.
The runner shows up in Settings → Actions → Runners as Idle, with the labels self-hosted, Windows and X64. Workflows pick it with those labels.
The workflow
Add this file to your repository, with your own project name and engine folder:
name: Build
on:
push:
branches: [main]
pull_request:
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: [self-hosted, Windows]
timeout-minutes: 180
env:
UE_ROOT: C:\Program Files\Epic Games\UE_5.7
steps:
- uses: actions/checkout@v7
with:
lfs: true
clean: false
- name: Package
shell: cmd
run: >
"%UE_ROOT%\Engine\Build\BatchFiles\RunUAT.bat" BuildCookRun
-project="%GITHUB_WORKSPACE%\MyGame.uproject"
-platform=Win64 -clientconfig=Development
-build -cook -stage -pak -archive
-archivedirectory="%GITHUB_WORKSPACE%\Build"
-noP4 -unattended -utf8output
- uses: actions/upload-artifact@v7
with:
name: MyGame-Windows
path: Build/Windows
compression-level: 0
retention-days: 7What each part does:
onbuilds every push tomainand every pull request. On pull requests, the result shows up as a check.concurrencycancels a build that's still running when a newer commit arrives on the same branch, so the runner doesn't spend an hour on an outdated commit.runs-on: [self-hosted, Windows]sends the job to your machine instead of GitHub's.lfs: truedownloads your Git LFS files. Without it, the build gets small pointer files instead of your assets.clean: falsematters more than it looks. By default, checkout runsgit clean, which deletes every ignored file: yourBinaries,Intermediatefolder and derived data cache. Every build would then start from scratch. Withclean: false, builds only redo what changed.shell: cmdwith>runs the BuildCookRun command from Package from the command line as one line.upload-artifactattaches the packaged game to the run, so anyone with access to the repository can download it from the run's page.compression-level: 0skips compressing files that are already compressed, which is much faster for a game build.retention-dayscontrols how long builds count against your artifact storage.
Several platforms
Add a job per platform, each with its own -platform and artifact name. They run one after another on a single runner, or side by side if you add more runners.
Keeping it running
A self-hosted runner is a machine you own, so it needs the care any build machine does:
- Disk space.
Intermediate, the derived data cache and old builds grow over time. Clean the runner's_workfolder now and then, or when builds start failing on space. - Engine updates. When the project moves to a new engine version, install it on the runner and update
UE_ROOT. - One build at a time. A runner runs one job at a time. Pushes queue behind each other until you add more runners, or machines.
- It has to be on. If the machine is off or asleep, jobs wait in the queue.
- Antivirus. Excluding the runner's folder from real-time scanning makes builds noticeably faster.
When a build fails, the job log shows the full BuildCookRun output. Packaging errors, explained covers the common ones.
Or skip the runner
Unreal Cloud vs GitHub Actions
How a self-hosted runner compares with a hosted build service, and when each one is the better pick.
Unreal Cloud
Builds every push and pull request on our machines, with the engine already installed, and reports the result as a check on your pull request. No runner, no workflow file.
Upload an Unreal Engine build to Steam with steamcmd
Upload a packaged Unreal Engine build to a Steam beta branch with steamcmd: the build script, depots for each platform, Steam Guard on a build machine, and the common errors.
Speed up Unreal Engine builds
Why Unreal Engine 5 builds are slow, and the changes that make the biggest difference, from incremental builds to a shared derived data cache.