Automating Unity Builds with CI/CD

In this article, we’ll look at Unity APIs and supporting tools that can help you create a robust CI/CD pipeline for your projects.

A complete example project – including the Unity project, Jenkins configuration, Docker containerization templates, and scripts – is available here: GitHub Repository

Why Automate Builds?

Save Development Time

Over the lifetime of a project, you’ll probably create hundreds or even thousands of builds for QA, internal testing, and release. Automating these steps saves countless hours compared to manual workflows, far outweighing the time required to build the automation system.

Ensure Consistency

Build pipelines eliminate human error. Instead of relying on checklists and memory, automation guarantees that every build follows the same sequence of steps.

Automate Expensive Tasks

A build pipeline is a great place to offload time-consuming or resource-heavy processes:

  • Asset compression and optimization
  • Static code/security analysis
  • Mesh and material merging
  • Shader/code generation
  • Full-quality light baking
  • Scene and prefab analysis

A Simple CI/CD Workflow Example

Once you’re satisfied with your local changes and commit them, it’s time to build for testing. With automation in place, a single click in the web interface triggers the build machine to:

  1. Checkout the Git repository
  2. Rebake lighting
  3. Run unit and play tests (with feedback on failures)
  4. Build for Android, iOS, and Web
  5. Deploy builds to local test devices
  6. Upload builds to Google Play and App Store (TestFlight)

For simplicity, I’ll skip APIs for pt. 6 details, handling signing keys, provisioning profiles, KMS, and secrets management deserve a separate article.

Now, let’s dive into the Unity APIs and system tools required to build such a pipeline.

Unity APIs and Tools for Build Automation

Running Unity Editor Scripts from CLI

You can launch the Unity Editor from the command line, specify a static method from your editor scripts to execute, and the editor will close automatically once the method finishes.

{UNITY_EDITOR_PATH} -batchmode -quit -projectPath {UNITY_PROJECT_PATH} {EDITOR_CLASS_NAME}.{EDITOR_CLASS_METHOD}

Docs: https://docs.unity3d.com/2020.1/Documentation/Manual/CommandLineArguments.html

Automating Unity Scene Manipulation

With a simple script, you can open, modify, and save Unity scenes directly – no manual editor interaction required:

EditorSceneManager.OpenScene(scenePath);  
// Do some stuff with a scene  
EditorSceneManager.SaveOpenScenes(); 

Docs: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/SceneManagement.EditorSceneManager.html

Unity Light Baking

Lighting can be baked programmatically on the currently opened scene:

Lightmapping.Bake();

Docs: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/Lightmapping.Bake.html

Unity Build Target Switching

You can change the active build target on the fly via script:

EditorUserBuildSettings.SwitchActiveBuildTarget(namedBuildTarget, target);

Docs: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/EditorUserBuildSettings.SwitchActiveBuildTarget.html

Unity Addressables

You can trigger an Addressables build directly from code:

AddressableAssetSettings.BuildPlayerContent();

Docs: https://docs.unity3d.com/Packages/com.unity.addressables@1.15/manual/BuildPlayerContent.html

Player Settings

You can modify player settings programmatically during the build process.
This is useful for tasks like retrieving an Android keystore from secure storage at build time, applying it, and then discarding changes afterward.

PlayerSettings.Android.keystoreName  = "PathToKeystore";
PlayerSettings.Android.keystorePass  = "";
PlayerSettings.Android.keyaliasName  = "KeyaliasName";
PlayerSettings.Android.keyaliasPass  = "";

Docs:

Unity Project Builds

You can build a Unity project for any supported platform directly from code.

Build Options:

var buildPlayerOptions = new BuildPlayerOptions {
    locationPathName = {PATH},
    target = {BUILD_TARGET},
    extraScriptingDefines = {SCRIPT_DEFINES},
    options = {BUILD_OPTIONS},
    scenes = {SCENES}
};

Docs: https://docs.unity3d.com/ScriptReference/BuildPlayerOptions.html

Build Pipeline:

BuildPipeline.BuildPlayer(buildPlayerOptions);

Docs: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/BuildPipeline.BuildPlayer.html

Unity Test Running

You can run both PlayMode tests and EditMode unit tests automatically as part of your build pipeline.

Run PlayMode Tests via Script:

var testRunner = ScriptableObject.CreateInstance<TestRunnerApi>();

var filter = new Filter {
    targetPlatform = {PLATFORM},
    testMode = TestMode.PlayMode
};

var executionSettings = new ExecutionSettings {
    filters = new[] { filter }
};

testRunner.Execute(executionSettings);

Docs: https://docs.unity3d.com/Packages/com.unity.test-framework@2.0/api/UnityEditor.TestTools.TestRunner.Api.TestRunnerApi.html

Run Unit Tests via Command Line:

${UNITY_PATH} -runTests -batchmode -projectPath ${UNITY_PROJECT_PATH} -testPlatform EditMode -logfile stdout -testResults 

Docs: https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/reference-command-line.html

Building an iOS Project from the Command Line

You can build and package iOS projects directly from the command line using xcodebuild.

1. Create an XCArchive

xcodebuild -workspace {WORKSPACE_PATH} -scheme {SCHEME_NAME} -archivePath {XCARCHIVE_PATH} archive

2. Prepare Export Options (.plist)
Create a .plist file with your desired export settings:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plistPUBLIC"-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plistversion="1.0">
<dict>
    <key>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>{YOUR_TEAM_ID}</string>
    <key>uploadBitcode</key>
    <true/>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

3. Build the .ipa File

xcodebuild -exportArchive -archivePath {XCARCHIVE_PATH} -exportPath {EXPORT_PATH} -exportOptionsPlist {OPTIONS_PLIST_PATH}

Docs: Apple Technical Note TN2339

Installing Test Builds on Local iOS Devices

The easiest way to install .ipa files locally is with Apple Configurator, which includes the cfgutil tool:

1. Install Apple Configurator 2 – https://apps.apple.com/us/app/apple-configurator/id1037126344?mt=12

2. Add cfgutil to your PATH:

sudo ln -s "/Applications/Apple Configurator 2.app/Contents/MacOS/cfgutil" /usr/local/bin/cfgutil

3. Install the .ipa on a connected device (via USB):

cfgutil install-app {IPA_PATH}

Wireless installation on iOS devices is cumbersome and unreliable. In most cases, it’s far easier to automate TestFlight uploads and distribute builds that way.

Installing Test Builds on Local Android Devices

You can install .apk files directly onto Android devices connected by USB cable or over a local wireless connection.

Wireless setup:

adb pair {IP}:{PORT}
adb connect {IP}:{PORT}
adb install {APK_PATH}
adb shell am start -n {APP_PACKAGE}/{ENTRY_POINT}

Docs: https://developer.android.com/tools/adb

App Store TestFlight & Google Play Internal Testing Upload Automation

Simple usage examples, provided without context …

AppStore

xcrun notarytool submit {IPA_PATH} --apple-id {APPLE_ID_EMAIL} --team-id {TEAM_ID} --password {APP_SPECIFIC_PASSWORD}

Google Play

gcloud auth activate-service-account --key-file={SERVICE_ACCOUNT_JSON} --project={GCP_PROJECT_ID}

gcloud firebase appdistribution:distribute {AAB_PATH} --app {FIREBASE_APP_ID} --testers {TESTERS} --release-notes {RELEASE_NOTE}

Wrapping Up

The APIs and examples above cover most of what you’ll need to assemble a custom Unity build pipeline. Add a bit of Git integration, Slack notifications, etc, and you’ve got a simple yet powerful automated workflow.

The final piece is choosing a tool to tie everything together. In my example, I’ve used Jenkins. While it may feel a little bit dated, its rich plugin ecosystem and ability to easily leverage external worker machines (e.g., running light baking on a Windows PC with a dedicated GPU) still make it a solid choice for local, self-hosted setups.

That said, almost any modern CI/CD solution can do the job just as well – the APIs and scripts remain the same.