Unreal Cloud Docs
Guides

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 Intermediate folder, 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 true

Before 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:

.github/workflows/build.yml
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: 7

What each part does:

  • on builds every push to main and every pull request. On pull requests, the result shows up as a check.
  • concurrency cancels 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: true downloads your Git LFS files. Without it, the build gets small pointer files instead of your assets.
  • clean: false matters more than it looks. By default, checkout runs git clean, which deletes every ignored file: your Binaries, Intermediate folder and derived data cache. Every build would then start from scratch. With clean: false, builds only redo what changed.
  • shell: cmd with > runs the BuildCookRun command from Package from the command line as one line.
  • upload-artifact attaches the packaged game to the run, so anyone with access to the repository can download it from the run's page. compression-level: 0 skips compressing files that are already compressed, which is much faster for a game build. retention-days controls 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 _work folder 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

On this page