Unity Devcontainer Licensing (Codespaces and Local Dev Containers)¶
Unity will not compile without an activated license, and every container rebuild looks like a brand new machine to Unity's licensing service. This guide gets scripts/unity/compile.sh and scripts/unity/run-tests.sh running in a Codespace or a local dev container, and tells you which error message means what.
Start by asking the repo what is missing -- this runs no Docker and takes a second:
| Bash | |
|---|---|
Fix whatever it reports, then run npm run unity:compile.
Pick an activation path¶
scripts/unity/run-unity-docker.sh supports three, and picks between them from which variables are set:
| License | Set these | Notes |
|---|---|---|
| Personal (recommended) | UNITY_EMAIL, UNITY_PASSWORD | Online activation. Personal cannot use .alf / .ulf at all. |
| Pro / Plus | UNITY_SERIAL, UNITY_EMAIL, UNITY_PASSWORD | All three are required; an incomplete set fails early. |
Manual .ulf | UNITY_LICENSE (or .unity-secrets/license.ulf) | Serial-based licenses only, and machine-specific -- see below. |
Two optional variables tune the timeouts (seconds): UNITY_LICENSE_ACTIVATION_TIMEOUT (default 300) and UNITY_LICENSE_RETURN_TIMEOUT (default 300).
Provide the credentials¶
Local dev container¶
Either export the variables in the shell you run the scripts from:
Or write them once to a file the scripts load automatically:
| Bash | |
|---|---|
That writes .unity-secrets/credentials.env, which is gitignored. run-unity-docker.sh loads it whenever the environment variables are not already set, and also picks up .unity-secrets/license.ulf into UNITY_LICENSE if that file exists.
Codespaces¶
- Open the repository on GitHub.
- Go to
Settings->Secrets and variables. - Choose
Codespaces(Codespaces-only) orActions(repository-level, reused by workflows). - Add
UNITY_EMAILandUNITY_PASSWORDwithNew repository secret.
Codespaces injects them as environment variables inside the devcontainer; run-unity-docker.sh forwards them into the Unity container with Docker -e flags. See Managing encrypted secrets for your codespaces.
What the script does with the license¶
It mounts a host cache into the Unity container, so an activation survives container restarts:
| Text Only | |
|---|---|
UNITY_TEST_PROJECT_DIR defaults to /home/vscode/.unity-test-project, so the cache lives in /home/vscode/.unity-test-project/.unity-license-cache. Override the cache location with UNITY_LICENSE_CACHE_DIR.
The cache holds Unity license identity — Unity_lic.ulf, UnityEntitlementLicense.xml and the entitlement audit log — so it must never sit under .artifacts, the tree CI uploads artifacts from. When the test project is itself inside .artifacts (the .unitypackage export puts it there), the cache moves to RUNNER_TEMP, or the system temp directory when that is unset, and a UNITY_LICENSE_CACHE_DIR pointing into .artifacts is refused. The rule matches the one the Windows runner already follows for its activation log, and one derivation in scripts/unity/lib/license-cache-dir.sh answers it for every Docker entry point.
Before running any Unity command, it verifies that at least one of these exists, and fails early pointing back here if none do:
| Text Only | |
|---|---|
Online activation also writes its raw log to /root/.config/unity3d/.activation-<timestamp>.log. That directory is the mounted cache, so the log survives the run and can be attached to a Unity support ticket.
Activation failures are classified from the activation log, and online activation is never re-run: a hard licensing rejection fails fast and deliberately skips the .ulf fallback, while a transient or network failure -- and any unconfirmed outcome the script cannot classify -- falls back to the .ulf in UNITY_LICENSE when one is set. A machine-registration problem tries that same .ulf once and otherwise stops with an actionable message. With no .ulf available, each of those paths fails the run.
After a Pro serial activation starts, the Docker wrapper attempts license return on every exit, including a partial activation that never produces a usable license file. The failed activation still fails the run. A successful return command is not sufficient cleanup evidence: CI retains its central confirmation gate and quarantines an unconfirmed return. See #724.
Troubleshooting by error message¶
Found 0 entitlement groups or com.unity.editor.headless was not found¶
| Text Only | |
|---|---|
This is a licensing-service decision about your account, not a missing command-line flag. The repo already runs Unity with the correct headless flags (-batchmode -nographics -quit), and there is no "headless toggle" in the Unity dashboard to turn on. Switching the build target to Dedicated Server does not help either: that target is for built player binaries, while these scripts run unity-editor in headless mode, which still needs an editor license.
Usual causes are the wrong Unity account for the license, a Personal account attempting a Pro-only path, or an expired entitlement. Verify the account can activate the license type you are asking for, confirm UNITY_SERIAL / UNITY_EMAIL / UNITY_PASSWORD agree for Pro, re-run, and check that artifacts appear in the cache. If it persists, open a Unity support ticket with the saved activation log.
No license activation found for this computer or No ULF license found¶
The container's machine identity is not registered with your Unity account -- typical on the first run in a fresh container or Codespace. If UNITY_LICENSE is set, run-unity-docker.sh tries that .ulf once, in case it was generated for this machine, and continues when it produces a license artifact. If it does not -- or if no .ulf was supplied at all -- the run stops there, because Unity Personal cannot recover through manual .alf upload.
- Personal: activate through Unity Hub on an interactive machine, or attach the saved activation log to a Unity support request.
- Serial-based license: follow Manual activation and place the result at
.unity-secrets/license.ulf.
Machine bindings don't match¶
| Text Only | |
|---|---|
.ulf files are encrypted with machine-specific hardware identifiers, so they cannot move between computers, between Docker containers (each container is a new "machine"), or between Codespaces instances. Rebuilding the devcontainer invalidates one too.
Use online activation instead (UNITY_EMAIL + UNITY_PASSWORD). It issues a machine-specific license inside the container, caches the artifact, reuses it on later runs without re-authenticating, survives image rebuilds, and behaves the same in Codespaces, CI and local containers.
Token not found in cache, repeatedly¶
The first activation probably failed silently -- look for the entitlement errors above. Then:
| Bash | |
|---|---|
If npm run unity:validate passes but compilation still fails, run Unity directly and read the full log:
| Bash | |
|---|---|
Manual activation (serial licenses only)¶
Unity Personal cannot use this path. For a paid license with a serial key:
- Generate the activation file:
| Bash | |
|---|---|
This runs scripts/unity/generate-activation.sh, which requires UNITY_SERIAL, spins up a Docker container, calls -createManualActivationFile, and copies the result to .unity-secrets/manual-activation.alf. A plain compile run does not produce one.
- Upload the
.alfat https://license.unity3d.com/manual and log in with your Unity account. - Enter your serial, download the
.ulf, and save it as.unity-secrets/license.ulf. - Retry:
| Bash | |
|---|---|
scripts/unity/retry-license.sh checks that .unity-secrets/license.ulf is present, clears stale cached license artifacts so Unity re-reads the new file, and re-runs compile.sh.
The .ulf you get back only works on the machine that produced the .alf.
What survives a devcontainer rebuild¶
| Item | Persists? | Why |
|---|---|---|
.unity-secrets/credentials.env | Yes | Gitignored workspace file, survives all rebuilds |
.unity-license-cache/ directory | Yes | Docker persistent volume |
License artifacts (.ulf / .xml) | Yes | Stored inside that persistent cache |
/root/.local/share/unity3d inside container | No | Recreated from the persistent volume on each run |
| Docker image layers | No | Rebuilt each time, though cached locally |
So after one successful activation, later compiles reuse the cached license and do not re-authenticate. .devcontainer/post-create.sh sets the cache directory permissions when the container is created, and .devcontainer/post-start.sh re-asserts ownership of ~/.unity-test-project on every start if Docker has reset it. Unity still contacts the network to confirm the license is active.
Checklist¶
- Run
npm run unity:validatefirst. - Confirm the credentials are visible to the shell running the script, or present in
.unity-secrets/credentials.env(and.unity-secrets/license.ulffor the manual path). - Confirm
~/.unity-test-project/.unity-license-cache/exists and is writable. - Match any Unity error against Troubleshooting by error message before retrying -- entitlement and machine-registration failures do not resolve on a retry.
- Capture the activation log from
/root/.config/unity3d/before opening a support ticket.