Docker 기반 Unity 빌드 파이프라인 구축: 팀 전원 100% 동일한 빌드 아티팩트 보장하기
로컬 환경 편차로 발생하는 Unity 빌드 불일치 문제를 해결하기 위해 Docker 컨테이너로 빌드 환경을 표준화하고 GameCI 및 라이선스 자동화, 캐시 최적화를 구축하는 실무 가이드입니다.

핵심 요약
개발자 및 QA 머신의 OS 업데이트, SDK/NDK 버전 불일치, 캐시 오염으로 인해 발생하는 ‘내 컴퓨터에서는 잘 빌드되는데’ 문제를 해결하는 가장 확실한 방법은 Unity 에디터와 빌드 툴체인을 Docker 컨테이너로 격리·표준화하는 것입니다. unityci/editor 공식/커뮤니티 이미지를 기반으로 라이선스 인증을 비동기 주입하고 프로젝트의 Library 폴더를 도커 볼륨으로 영속화하면 로컬과 CI 서버 어디서나 바이트 레벨에서 일치하는 결정론적(Deterministic) 아티팩트를 생산할 수 있습니다.
왜 로컬 머신마다 Unity 빌드 결과가 달라질까?
게임 개발 스튜디오에서 빌드 배포 단계에 가장 빈번하게 마주치는 이슈는 개발자 로컬 PC, QA 검증용 머신, CI 빌드 서버 간의 빌드 결과물 차이입니다. 분명 동일한 Git 커밋 해시에서 빌드했음에도 불구하고 특정 머신에서 생성된 바이너리에서만 런타임 크래시가 발생하거나 셰이더 컴파일 결과가 달라져 렌더링 아티팩트가 깨지는 현상이 발생합니다.
이러한 현상이 발생하는 기술적 원인은 크게 4가지 환경적 편차 때문입니다.
- 호스트 OS 및 빌드 도구 툴체인의 파편화: Windows 10/11의 빌드 버전 차이, Visual Studio MSVC 툴셋(v142 vs v143) 버전, Android NDK/JDK의 세부 마이너 패치 차이로 인해 IL2CPP 변환 C++ 코드의 컴파일 바이너리 심볼이 달라집니다.
- Unity Library 캐시 오염(Cache Invalidation): 각 머신의 로컬
Library폴더에 남아 있는 이전 타깃 플랫폼의 캐시 데이터나 에셋 메타데이터 잔재가 에셋 번들 및 바이너리 해시에 영향을 줍니다. - Unity 패키지 매니저(UPM) 및 커스텀 네이티브 플러그인 종속성: 머신에 전역 설치된 환경변수, CMake, Python 버전 차이로 네이티브
.dll/.so라이브러리가 다르게 링크됩니다. - 에디터 세부 빌드 설정 누락: GUI 에디터에서 수동으로 빌드할 때 체크박스 하나(Development Build, Script Debugging, Code Stripping Level)가 누락되는 휴먼 에러가 발생합니다.

이 문제를 근본적으로 해결하려면 빌드 머신의 호스트 환경에 의존하지 않고 Unity 에디터 버전, 대상 플랫폼 SDK(Android NDK, OpenJDK), 빌드 스크립트 실행 환경 전체를 Docker 컨테이너 이미지로 고정해야 합니다.
Docker 기반 Unity 빌드 아키텍처 개요
컨테이너 기반 Unity 빌드는 호스트 시스템의 종속성을 배제하고 Linux 기반의 경량 컨테이너 내부에서 Unity를 헤드리스(Headless, -batchmode -nographics) 모드로 실행하여 빌드 아티팩트를 출력하는 구조를 가집니다.
flowchart LR
subgraph Host [Host Machine / CI Runner]
Repo[Git Repository]
Vol[Host Library Cache]
Lic[Unity License / ULF]
end
subgraph Docker ["Docker Container (unityci/editor)"]
Engine[Unity Headless Engine]
NDK[Bundled Android NDK/JDK]
Script[BuildCommand.cs]
end
subgraph Output [Artifacts]
APK[Target Binary / APK / AAB / Standalone]
AB[AssetBundles]
Log[build.log]
end
Repo -->|Source Mount| Docker
Vol <-->|Volume Mount| Docker
Lic -->|Env / Mount| Engine
Engine --> Script
Script --> NDK
Script --> Output
| 구분 | 기존 로컬/베어메탈 빌드 | Docker 컨테이너 빌드 |
|---|---|---|
| 환경 격리 수준 | 호스트 OS 및 전역 설치 툴에 종속 | 컨테이너 내부 툴체인으로 100% 독립 |
| 빌드 재현성 | 머신별 빌드 파편화 및 캐시 오염 발생 | 동일 이미지 기반 결정론적(Deterministic) 빌드 |
| 신규 머신 셋업 시간 | 에디터, 모듈, VS 툴셋 수동 설치 (3~5시간) | Docker Image Pull 및 실행 (10분 이내) |
| CI/CD 확장성 | 전용 베어메탈 빌드 머신 의존성 높음 | 클라우드 러너(GitHub Actions, GitLab Runner) 무제한 확장 |
Unity Docker 빌드 환경 구축 4단계 가이드
1단계: 프로젝트에 맞는 베이스 이미지 선정
처음부터 Unity Linux 에디터 설치 스크립트를 작성하는 것보다 오픈소스 표준인 GameCI에서 관리하는 공식 컨테이너 이미지를 활용하는 것이 안정적입니다. 프로젝트의 Unity 버전과 빌드 타깃 플랫폼(Standalone Linux, Android, iOS, WebGL 등)에 부합하는 태그를 선택합니다.
- Docker Hub 레포지토리:
unityci/editor - 태그 규칙:
unityci/editor:ubuntu-{Unity버전}-{타깃플랫폼}-{버전}(예:unityci/editor:ubuntu-2022.3.20f1-android-3.0.0)
2단계: C# 헤드리스 빌드 스크립트 작성
GUI를 띄우지 않고 명령줄 인자(CLI arguments)를 파싱하여 빌드를 수행하는 정적 C# 스크립트를 에디터 폴더(Assets/Editor/BuildCommand.cs)에 작성합니다.
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
public static class BuildCommand
{
public static void PerformBuild()
{
Debug.Log("[CI/CD] Starting Headless Build Pipeline...");
string[] scenes = EditorBuildSettings.scenes
.Where(s => s.enabled)
.Select(s => s.path)
.ToArray();
string buildPath = GetArg("-customBuildPath") ?? "Builds/StandaloneLinux64/GameBinary";
BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = buildPath,
target = target,
options = BuildOptions.None
};
BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
BuildSummary summary = report.summary;
if (summary.result == BuildResult.Succeeded)
{
Debug.Log($"[CI/CD] Build Succeeded! Total Size: {summary.totalSize} bytes, Time: {summary.totalTime}");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[CI/CD] Build Failed with {summary.totalErrors} errors.");
EditorApplication.Exit(1);
}
}
private static string GetArg(string name)
{
string[] args = Environment.GetCommandLineArgs();
for (int i = 0; i < args.Length; i++)
{
if (args[i] == name && args.Length > i + 1)
{
return args[i + 1];
}
}
return null;
}
}
#endif
3단계: Unity 라이선스 활성화 처리
Docker 내부에서 Unity를 실행하려면 라이선스 인증이 필수적입니다. Unity Personal(무료) 라이선스와 Professional(유료) 라이선스의 적용 방식이 다릅니다.
- Unity Personal: GameCI의 활성화 도구를 통해 생성된
.ulf라이선스 파일 내용을 환경변수(UNITY_LICENSE) 또는 마운트 파일로 주입합니다. - Unity Pro / Enterprise: 시리얼 키, 유저네임, 패스워드를 환경변수로 전달하여 컨테이너 기동 시 활성화하고 빌드 종료 후 반납(Return)합니다.

4단계: Docker Run 명령 및 자동화 스크립트 작성
호스트의 소스 코드 경로와 캐시 디렉터리를 볼륨으로 연결하고 헤드리스 모드로 빌드를 호출합니다.
#!/usr/bin/env bash
set -euo pipefail
PROJECT_PATH="$(pwd)"
BUILD_OUTPUT_PATH="${PROJECT_PATH}/Builds/Android"
CACHE_PATH="${PROJECT_PATH}/.docker-cache/Library"
mkdir -p "${BUILD_OUTPUT_PATH}" "${CACHE_PATH}"
IMAGE_NAME="unityci/editor:ubuntu-2022.3.20f1-android-3.0.0"
docker run --rm \
-e UNITY_LICENSE="${UNITY_LICENSE_CONTENT}" \
-v "${PROJECT_PATH}:/project:rw" \
-v "${CACHE_PATH}:/project/Library:rw" \
-w /project \
"${IMAGE_NAME}" \
unity-editor \
-batchmode \
-nographics \
-quit \
-projectPath /project \
-buildTarget Android \
-executeMethod BuildCommand.PerformBuild \
-customBuildPath /project/Builds/Android/game.apk \
-logFile /project/Builds/build.log
echo "Docker Unity Build Completed Successfully."
빌드 속도 향상을 위한 Library 캐시 및 스토리지 최적화
컨테이너 환경의 유일한 단점은 매 실행마다 컨테이너 레이어가 초기화되어 에셋 리임포트(Asset Reimport)가 발생해 빌드 시간이 폭증할 수 있다는 점입니다. 100GB 단위의 프로젝트에서 매번 처음부터 임포트하면 빌드당 수 시간이 소요됩니다.
이를 해결하려면 호스트 볼륨을 활용한 디렉터리 바인딩과 Unity Accelerator를 결합해야 합니다.
Library볼륨 마운트 영속화: 컨테이너의/project/Library경로를 호스트의 영속적 캐시 스토리지에 바인딩하여 이전 임포트 결과를 재사용합니다.- 멀티스테이지 에셋 캐시 분리: 텍스처, 오디오, 셰이더 등 변경 빈도가 낮은 에셋 번들 빌드 단계와 스크립트/바이너리 빌드 단계를 분리합니다.
- Unity Accelerator(Asset Server Cache) 연동: 사내 로컬 네트워크망에 Unity Accelerator 데몬을 띄우고 컨테이너 실행 시
-CacheServerIPAddress인자를 주입합니다.
unity-editor -batchmode -nographics \
-CacheServerIPAddress 192.168.1.100:10080 \
-CacheServerEnableDownload true \
-CacheServerEnableUpload true \
-executeMethod BuildCommand.PerformBuild
자주 묻는 질문 (FAQ)
Q1. Linux Docker 컨테이너에서 Windows용 Standalone 실행 파일(.exe)을 빌드할 수 있나요?
네, 가능합니다. Unity Linux 에디터에 windows-mono 또는 windows-il2cpp 타깃 지원 모듈이 설치된 이미지를 사용하면 됩니다. GameCI의 unityci/editor:ubuntu-2022.3.xx-windows-mono-x.x.x 이미지를 사용하고 빌드 타깃을 StandaloneWindows64로 지정하면 Linux 호스트 및 컨테이너 상에서도 완벽하게 .exe 바이너리를 크로스 컴파일할 수 있습니다.
Q2. IL2CPP 빌드 시 Docker 내부에서 발생하는 메모리 부족(OOM)은 어떻게 방지하나요?
IL2CPP는 수만 개의 C# 스크립트를 방대한 C++ 코드로 변환한 후 Clang/GCC로 병렬 컴파일하므로 피크 시 메모리 사용량이 급증합니다. Docker 실행 시 --memory 제한을 호스트 시스템의 여유 용량(최소 16GB~32GB 권장)으로 충분히 할당하고 swap 메모리를 지정하거나 Unity Editor 스크립트에서 PlayerSettings.SetIl2CppCompilerConfiguration(NamedBuildTarget.Android, Il2CppCompilerConfiguration.Release) 설정을 점검하여 과도한 디버그 심볼 생성을 방지해야 합니다.
Q3. iOS 빌드(.ipa)도 Linux Docker 컨테이너에서 완료할 수 있나요?
Unity의 Xcode 프로젝트 익스포트 단계까지만 가능합니다. Apple의 정책 및 툴체인 종속성으로 인해 최종 .ipa 서명 및 바이너리 컴파일은 반드시 macOS 환경의 xcodebuild를 거쳐야 합니다. 따라서 CI 파이프라인 구성 시 [Linux Docker: Unity -> Xcode 프로젝트 생성] -> [macOS Runner: fastlane/xcodebuild -> IPA 아카이브]의 2단계 분산 파이프라인으로 설계하는 것이 표준입니다.
요약 및 도입 체크리스트
Docker 기반 Unity 빌드 환경 구축은 초기 설정에 러닝 커브가 존재하지만 한 번 구축해 두면 팀 내 모든 구성원과 CI/CD 파이프라인이 100% 동일한 빌드 산출물을 보장받게 됩니다.
- 프로젝트 Unity 버전과 일치하는
unityci/editor베이스 이미지 확보 - GUI 종속성이 제거된 헤드리스
BuildCommand.cs에디터 스크립트 작성 - CI 환경변수를 통한 라이선스 주입 및 빌드 후 라이선스 해제 로직 구현
- 에셋 리임포트 방지를 위한
Library폴더 호스트 볼륨 마운트 설정 - 사내 Unity Accelerator 캐시 서버 연동을 통한 빌드 파이프라인 가속



