Overview

Continuous integration (CI) means that every change pushed to the shared repository is automatically built and tested on a clean machine. For a Unity team, it answers the question “does the project still open, pass its tests and build a player on a machine that is not mine?” before a pull request is merged. It catches problems that a local editor hides: a script that only compiles because of an uncommitted file, a missing .meta file, a test that someone forgot to run, or a build that fails for the target platform.

GameCI is an open-source project that packages the Unity Editor in Docker images and provides ready-made GitHub Actions for Unity (GameCI, Getting started):

  • game-ci/unity-test-runner runs the project’s Edit Mode and Play Mode tests (written with Unity’s Test Framework) and can publish the results as a check on the commit or pull request (GameCI, Test runner).
  • game-ci/unity-builder builds a player for a chosen targetPlatform, for example WebGL, StandaloneWindows64 or StandaloneLinux64, and writes it to a build/ folder (GameCI, Builder).

GameCI fits into the branching models described in git-branching: the workflow runs on every pull request into the integration branch (main in GitHub Flow, develop in Git Flow), and a branch rule makes the job a required status check, so a pull request cannot be merged while the tests or build fail.


Setup

1. Activate a Unity licence for CI

The Unity Editor will not run without an activated licence, even on a build server. GameCI reads the licence from GitHub repository secrets (GameCI, Activation):

LicenceSecrets to create
Unity Personal (free)UNITY_LICENSE (the full contents of the .ulf licence file), UNITY_EMAIL, UNITY_PASSWORD
Unity Plus / ProUNITY_SERIAL (from the Unity subscriptions page), UNITY_EMAIL, UNITY_PASSWORD

To obtain the .ulf file for a Personal licence, GameCI’s instructions are: sign in to Unity Hub with the account the CI will use, go to Preferences → Licenses → Add → Get a free personal license, then copy the file from:

  • Windows: C:\ProgramData\Unity\Unity_lic.ulf
  • macOS: /Library/Application Support/Unity/Unity_lic.ulf
  • Linux: ~/.local/share/unity3d/Unity/Unity_lic.ulf

GameCI warns that a licence can appear in Unity Hub without a .ulf file having been written, so you must actually complete the Add step; the folders may also be hidden. The licence is not tied to an editor version or operating system. Do not use the .ulf route if you hold a Plus or Pro licence (GameCI, Activation).

Practical advice (interpretation): for a student team, one person, or a dedicated team Unity account, activates the licence and creates the secrets. Never commit the .ulf file or the password to the repository.

2. Add the secrets to GitHub

On the repository: Settings → Secrets and variables → Actions → New repository secret (in the “Security” section of the sidebar), once for each secret (GitHub, Using secrets in GitHub Actions).

3. Add a workflow file

Create .github/workflows/unity-ci.yml. The example below is adapted from GameCI’s simple example (GameCI, Getting started), triggered on pull requests into main or develop:

name: Unity CI
on:
  pull_request:
    branches: [main, develop]
jobs:
  test-and-build:                      # this name becomes the status check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { lfs: true }
      - uses: actions/cache@v4         # reuse Unity's Library folder
        with:
          path: Library
          key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
          restore-keys: Library-
      - uses: game-ci/unity-test-runner@v4
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with: { githubToken: "${{ secrets.GITHUB_TOKEN }}" }
      - uses: game-ci/unity-builder@v4
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with: { targetPlatform: WebGL }
      - uses: actions/upload-artifact@v4
        with: { name: Build, path: build }

Both GameCI steps need the same three licence secrets. GameCI’s example uses actions/cache@v3; @v4 is shown here as the current major version of that action. GameCI’s example assumes the Unity project is at the root of the repository; otherwise set projectPath (GameCI, Getting started; Test runner). The Unity version is read automatically from ProjectSettings/ProjectVersion.txt (unityVersion: auto, the default).

4. Make the job a required check

Open the first pull request so the workflow runs once, then add the job name (test-and-build above) under Require status checks to pass before merging in the branch’s ruleset or branch protection rule. See Protecting branches on GitHub in git-branching.


Usage

  • Every pull request into main or develop now shows the test-and-build check. A red cross means “fix this before merging”; the log of the failed step usually names the failing test or compiler error.
  • Test results: with githubToken set, the test runner publishes its results as a check run on the pull request. This needs the workflow to have read and write permissions (Settings → Actions → General → Workflow permissions). Results are also written to an artifacts folder, which you can upload with actions/upload-artifact using if: always() so that results are kept even when tests fail (GameCI, Test runner).
  • Build output: the builder writes to build/<targetPlatform>/ by default; the upload step makes the build downloadable from the workflow run page, which is handy for playtesters (GameCI, Builder).
  • Several platforms: GameCI’s builder documentation shows a strategy.matrix.targetPlatform job that builds each platform in parallel with fail-fast: false (GameCI, Builder).
  • No tests yet? The workflow is still worth having for the build step alone. Adding even a handful of Edit Mode tests for core rules (damage, scoring, inventory) makes the check far more useful.

Gotchas

  • Licence errors are the most common first failure. Check that the secret names match exactly and that UNITY_LICENSE holds the whole .ulf file contents, not its path (GameCI, Activation).
  • Git LFS bandwidth. lfs: true downloads every LFS file on each run. GameCI notes that projects storing binaries in GitHub’s LFS hosting “will want to cache them to avoid consuming massive amounts of bandwidth” and provides an LFS-caching example (GameCI, Getting started). See the LFS quota notes in git-github-unity.
  • Cache the Library folder. Without it, Unity re-imports every asset on every run. GameCI’s builder docs suggest including the target platform in the cache key when building several platforms, and note that a cache entry cannot be overwritten once its exact key exists (GameCI, Builder).
  • Actions minutes on private repositories. GitHub Actions is free on public repositories using standard GitHub-hosted runners; private repositories draw on a monthly allowance (2,000 minutes on GitHub Free), and the minutes are charged to the repository owner (GitHub, GitHub Actions billing; GitHub’s plans). Unity builds take several minutes each, so triggering on pull requests rather than on every push saves allowance.
  • Pull requests from forks get no secrets. “With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository” (GitHub, Using secrets in GitHub Actions). Teams that add each other as collaborators and push branches to the same repository avoid this; a fork-based workflow will fail licence activation.
  • Tests run on Linux runners. GameCI states that tests are currently only compatible with Linux hosts (GameCI, Getting started). A Linux runner can build a Windows Mono player, but targets needing native toolchains (for example IL2CPP for some platforms, or iOS) need a compatible runner (GameCI, Builder).
  • Unique job names. Required status checks are matched by job name; reusing a job name in several workflows can leave pull requests blocked by an ambiguous check (GitHub, About protected branches).
  • Editor version support (tentative). GameCI builds Docker images for specific Unity versions. If your exact editor version has no image yet, the job fails before Unity starts; check GameCI’s Docker versions list or move the project to a supported patch release.

External references