Unity Builds on GitHub Actions
Run Unity builds on GitHub Actions with WarpBuild Windows runners from 4 to 32 vCPU, 256GB SSDs, $0.016 to $0.128 per minute, and a cached Library folder.
Last verified:
A Unity build on GitHub Actions needs a Windows runner with cores for shader compilation and the IL2CPP native compile, a disk large enough to hold the Editor plus the project's Library folder, and a cache that keeps the asset import from running cold on every job. WarpBuild provides Windows runners from 4 to 32 vCPUs, every size on a 256GB SSD, at $0.016 to $0.128 per minute, selected by pointing runs-on at a label such as warp-windows-latest-x64-16x.
This page covers the workflow YAML for a Windows player build with the Library folder cached, the macOS path for iOS player builds, the Windows catalog rows against GitHub-hosted list prices, the four bottlenecks that dominate Unity wall clock time, and how to size storage before a job runs out of room.
Overview
A Unity repository usually settles into three job shapes.
The first runs on every pull request: edit-mode tests, static analysis, and often a fast development player build for one target. The second is the platform matrix on merge to the main branch: a player build per shipping target, each producing an artifact for QA. The third is the release job, which adds symbol upload, addressable bundle publishing, and store submission.
Most of that work lives on Windows. The Windows Editor covers Windows, Android, WebGL, and console targets, and it hosts the MSVC toolchain that IL2CPP invokes for the native compile step. The iOS player is the exception: Unity exports an Xcode project and xcodebuild finishes the archive, so that job has to run on macOS.
WarpBuild provides Linux x64, Linux ARM64, macOS, and Windows runners, so both jobs live in one workflow file and differ only in the warp- label on runs-on. Every runner is an ephemeral VM, freshly allocated for the job and destroyed afterward, and the Windows images carry the same tooling as GitHub-hosted Windows images. The full matrix with sizes and prices is in the cloud runner documentation and on the Windows runner hub.
No hosted runner image ships the Unity Editor, so the Editor install and its activation are steps your workflow owns. This page stays on runner sizing, storage, and caching, which is where the recoverable minutes are. Studios shipping on a second engine hit the same Windows sizing questions from a different direction; the Unreal Engine builds page covers that pipeline.
Configuration
Here is a Windows player build on warp-windows-latest-x64-16x with the Library folder cached, followed by the macOS job that produces the iOS archive.
name: unity
on:
pull_request:
push:
branches: [main]
env:
UNITY_VERSION: "6000.0.42f1"
jobs:
windows-player:
runs-on: warp-windows-latest-x64-16x
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Install the Unity Editor
shell: pwsh
run: ./ci/install-unity.ps1 -Version $env:UNITY_VERSION
- uses: actions/cache@v4
with:
path: |
Library/Artifacts
Library/ArtifactDB
Library/SourceAssetDB
Library/ShaderCache
Library/PackageCache
key: unity-win64-${{ env.UNITY_VERSION }}-${{ hashFiles('Packages/packages-lock.json', 'ProjectSettings/ProjectVersion.txt') }}
restore-keys: |
unity-win64-${{ env.UNITY_VERSION }}-
- name: Build the Windows player
shell: pwsh
run: |
& "$env:UNITY_PATH" `
-batchmode -nographics -quit `
-projectPath . `
-buildTarget StandaloneWindows64 `
-executeMethod BuildScript.BuildWindowsPlayer `
-logFile -
- uses: actions/upload-artifact@v4
with:
name: windows-player
path: Build/StandaloneWindows64
retention-days: 7
compression-level: 0
ios-player:
runs-on: warp-macos-15-arm64-12x
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Install the Unity Editor
run: ./ci/install-unity.sh "$UNITY_VERSION"
- uses: actions/cache@v4
with:
path: |
Library/Artifacts
Library/ArtifactDB
Library/SourceAssetDB
Library/ShaderCache
Library/PackageCache
key: unity-ios-${{ env.UNITY_VERSION }}-${{ hashFiles('Packages/packages-lock.json', 'ProjectSettings/ProjectVersion.txt') }}
restore-keys: |
unity-ios-${{ env.UNITY_VERSION }}-
- name: Export the Xcode project
run: |
"$UNITY_PATH" \
-batchmode -nographics -quit \
-projectPath . \
-buildTarget iOS \
-executeMethod BuildScript.ExportXcodeProject \
-logFile -
- name: Build the archive
run: |
xcodebuild -project Build/iOS/Unity-iPhone.xcodeproj \
-scheme Unity-iPhone \
-configuration Release \
-archivePath "$RUNNER_TEMP/Unity-iPhone.xcarchive" \
archiveFive details matter here.
Cache Library with actions/cache, and keep the path narrow. The WarpBuild cache is available on Linux runners and is not supported on Windows runners, so Windows jobs use GitHub's cache service. That service caps a repository at 10 GB across all entries, per GitHub's caching documentation checked on 2026-08-13, and a full Library folder on a mid-size project passes that on its own. The five subtrees above are the ones that pay back the import step. Library/Bee and the IL2CPP intermediate directories are large and rebuild quickly, so leave them out when space is tight.
Give every build target its own cache key. Texture compression, audio format, and shader variants all differ per platform, so a Library folder imported for StandaloneWindows64 is wrong for iOS. The unity-win64- and unity-ios- prefixes keep the two jobs from overwriting each other, and the restore-keys prefix lets a job start from the newest entry for that target when the lock file changes.
Hash the lock file and the Editor version. Packages/packages-lock.json changes when a package dependency moves, ProjectSettings/ProjectVersion.txt changes when the Editor version moves, and either one invalidates imported artifacts. Keying on both means the cache turns over exactly when the imported output stops being valid.
Check out LFS. Most Unity repositories keep textures, audio, and models in Git LFS. Without lfs: true the working tree holds pointer files, the import produces broken artifacts, and the build fails late with an error that points at the asset rather than the checkout.
Take one target per job. Each job in the matrix restores its own cache and stays on one build target for its whole life. Per-minute billing means four 20 minute jobs in parallel cost the same as one 80 minute job, while returning results four times sooner.
On the Windows side, the IL2CPP native compile step calls the Visual Studio toolchain that ships in the image. The image families and the toolset each one carries are listed on the Visual Studio images page, and the same toolchain caching tactics that apply to native code apply here, covered in the MSBuild caching guide.
Sizing
The Windows catalog, with per-minute rates from the pricing page and GitHub's published rates for the same shapes:
| Runner label | vCPU | Memory | Storage | WarpBuild per minute | GitHub-hosted equivalent | GitHub per minute | Lower by |
|---|---|---|---|---|---|---|---|
warp-windows-latest-x64-4x | 4 | 16GB | 256GB SSD | $0.016 | 4-core Windows larger runner | $0.022 | 27 percent |
warp-windows-latest-x64-8x | 8 | 32GB | 256GB SSD | $0.032 | 8-core Windows larger runner | $0.042 | 24 percent |
warp-windows-latest-x64-16x | 16 | 64GB | 256GB SSD | $0.064 | 16-core Windows larger runner | $0.082 | 22 percent |
warp-windows-latest-x64-32x | 32 | 128GB | 256GB SSD | $0.128 | 32-core Windows larger runner | $0.162 | 21 percent |
Each row compares the same vCPU and memory shape on both sides. The 16 vCPU row is the arithmetic most Unity teams care about: ($0.082 - $0.064) / $0.082 = 22 percent lower list price. GitHub rates come from the GitHub Actions minute multipliers reference, checked on 2026-08-13. The warp-windows-2025-x64-<size> and warp-windows-2025-vs2026-x64-<size> label families come in the same four sizes at the same rates. Windows 2 vCPU runners were removed on June 8, 2026, so 4 vCPU is the smallest Windows size.
Worked cost model
Take a studio running 500 pull request pipelines a month, each one a 22 minute Windows player build on 16 vCPU, plus a nightly multi-platform build of 40 minutes on 32 vCPU across 60 nights.
| Job | Minutes per month | WarpBuild rate | WarpBuild cost | GitHub-hosted rate | GitHub-hosted cost |
|---|---|---|---|---|---|
| Pull request player build (16 vCPU) | 11,000 | $0.064/min | $704.00 | $0.082/min | $902.00 |
| Nightly platform matrix (32 vCPU) | 2,400 | $0.128/min | $307.20 | $0.162/min | $388.80 |
| Total | 13,400 | $1,011.20 | $1,290.80 |
The gap is $279.60 a month, about 22 percent, holding minutes equal on both sides.
Sizing the disk
Storage binds before memory does on Unity jobs. A single Windows job holds the Editor install, the checkout with every source asset pulled through LFS, the Library folder of imported artifacts, the shader cache, the IL2CPP and Burst intermediate output, the built player, and any addressable bundles. The Library folder alone frequently exceeds the size of the source assets it was imported from, because each asset is stored again in a target-specific format.
Every WarpBuild Windows size carries a 256GB SSD, against 150GB on the Linux x64 labels, which is the practical reason Unity work sits on Windows machines beyond toolchain compatibility. Runner storage is ephemeral and is deleted when the runner terminates, so the disk holds one job's working set and nothing else.
When the working set passes 256GB, the job fails partway through, and the message comes from whichever tool ran out of room, usually the asset importer, the native compile, or the artifact upload step, rather than naming the disk. Four things bring the working set back down: build one target per job so only one Library variant exists on disk, delete intermediate output between the build and upload steps, narrow the artifact path to what downstream jobs consume, and drop Library/Bee from the cached paths so a restore does not land data the job would rebuild anyway. Projects that stay above 256GB after all four, which usually means very large source asset libraries, can run on BYOC, where runners live in your own AWS, GCP, or Azure account and you configure the instance type and disk yourself. The BYOC documentation covers that setup.
Bottlenecks
Four things dominate Unity wall clock time on GitHub Actions.
1. Cold Library import. On a fresh VM with no cache, the Editor imports every asset in the project before it builds anything: textures recompressed for the target, meshes converted, audio transcoded, shaders parsed. On a large project this single phase can outweigh the build itself. A warm Library cache removes almost all of it, which is why the cache key and the target-specific prefixes above matter more than any other tuning on this page. Watch the cache hit rate in the job log; a key that turns over on every commit gives you a cold import every run.
2. Shader compilation. Shader variants multiply across every keyword combination the project enables, and the Editor compiles them during the player build. This is the phase that actually uses cores, so it drives the choice between the 16x and 32x labels. On the project side, the levers are keyword stripping in the graphics settings, shader variant collections that limit what gets built, and keeping Library/ShaderCache in the cached paths so a run recompiles only what changed.
3. Platform switching. Switching the build target inside one job forces a reimport of every asset that has a target-specific representation, which means paying most of the cold import again in the middle of a job that already looked warm. A matrix with one job per target avoids it entirely, at the cost of one cache restore per job. Under per-minute billing the parallel shape costs the same total and finishes sooner, so it wins on both counts.
4. Artifact upload. A player build plus symbol files and addressable bundles runs to several gigabytes, and actions/upload-artifact sits at the end of the job where everyone is waiting on it. Upload only what a downstream job or a QA build actually consumes, set compression-level: 0 for payloads that are already compressed so the step stops burning CPU for no size reduction, put symbols in a separate artifact with a shorter retention window, and push bundles straight to your own storage instead of routing them through the artifact service.
Telling these apart is a measurement problem. WarpBuild's CI observability streams system metrics from the runner and correlates them with GitHub Actions job logs, so a job stuck on single-threaded import looks visibly different from one saturating 16 vCPUs through shader compilation. The Action Debugger pauses a workflow and opens a session on the live runner, which is the quickest way to check what is actually on disk when a job dies during import.
Proof
The migration cost is the label. The Windows images carry the same tooling as GitHub-hosted Windows images, so the Editor install script, the MSVC toolchain that IL2CPP calls, and every marketplace action behave the same way after the runs-on line changes.
FAQ
Which WarpBuild Windows runner size should a Unity project use?
Start player builds on warp-windows-latest-x64-16x (16 vCPU, 64GB, 256GB SSD, $0.064 per minute). Keep edit-mode test jobs on warp-windows-latest-x64-8x at $0.032 per minute. Move to warp-windows-latest-x64-32x at $0.128 per minute when shader compilation and the IL2CPP native compile keep all 16 vCPUs busy for most of the build.
Can the WarpBuild cache hold the Unity Library folder on Windows?
No. The WarpBuild cache is available on Linux runners and is not supported on Windows runners. Windows jobs cache Library with actions/cache against GitHub's cache service, which caps a repository at 10 GB. Android, WebGL, and Linux player jobs can move to Linux labels where the WarpBuild cache applies.
How much disk does a Unity job need on a runner?
Every WarpBuild Windows size carries a 256GB SSD, against 150GB on the Linux x64 labels. A Unity job holds the Editor install, the checkout with its source assets, a Library folder that is often larger than the source assets, the shader cache, IL2CPP and Burst intermediate output, and the built player. Runner storage is ephemeral and is deleted when the runner terminates.
What does a Unity pipeline cost against GitHub-hosted Windows runners?
warp-windows-latest-x64-16x costs $0.064 per minute against $0.082 per minute for the 16-core Windows larger runner at the same 16 vCPU and 64GB shape, which is 22 percent lower list price (GitHub pricing, checked on 2026-08-13).
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.