/ 블로그 / GitLab CI iOS 빌드: 2026 원격 Mac 튜토리얼
ENGINEERING_BLOG · 2026.08.31

GitLab CI iOS 빌드: 2026 원격 Mac 튜토리얼

Linux Runner에서는 안드로이드 작업이 통과하지만 iOS 작업만 Xcode를 찾지 못합니다.
가장 빠른 해법은 전용 macOS 사용자 세션에 Shell executor 기반 GitLab Runner를 두고, Xcode·서명·Archive·TestFlight 업로드·재시작 복구까지 검증하는 것입니다.

이 글은 GitLab을 사용하지만 기존 Runner가 안드로이드나 백엔드 작업만 처리하는 독립 개발자를 위한 글입니다. 수동 Xcode Archive를 GitLab CI 흐름으로 바꾸려는 iOS 개발자, 상시 운영할 원격 Mac을 검토하는 소규모 팀에도 적합합니다.

SECTION 01첫 번째 지표: 호스트와 사용자 세션

GitLab CI iOS 빌드는 Linux Runner만으로 완성할 수 없습니다. iOS 빌드 단계에는 Xcode와 Apple SDK가 필요하므로 실제 작업은 macOS 호스트에 배정되어야 합니다. GitLab의 macOS Runner 서비스 문서는 macOS에서 Runner를 사용자 수준 LaunchAgent로 실행하는 방식을 설명합니다.

이 구조에서 “Runner 프로세스가 실행 중”이라는 사실과 “배포 작업을 처리할 수 있음”은 다릅니다. 로그인 세션, 사용자 Keychain, 그래픽 세션 접근이 끊기면 Runner가 온라인으로 표시되어도 서명이나 Archive가 실패할 수 있습니다.

재시작 뒤에는 다음 관계를 순서대로 확인합니다.

  1. 지정된 macOS 사용자로 로그인됩니다.
  2. 사용자 수준 LaunchAgent가 Runner를 시작합니다.
  3. GitLab 화면에서 Runner가 온라인으로 표시됩니다.
  4. 해당 태그를 가진 작업을 실제로 받습니다.
  5. Xcode 선택과 Keychain 접근이 성공합니다.
  6. 테스트용 작업이 아니라 Archive 작업이 끝까지 완료됩니다.

자동 로그인을 유일한 보안 대책으로 취급하면 안 됩니다. 전용 계정, 화면 잠금 정책, 원격 접속 권한, 복구 담당자를 함께 정해야 합니다.

SECTION 02도구 체인 기준선

같은 커밋을 다시 빌드하려면 Xcode 앱이 설치되어 있다는 사실만으로 부족합니다. 활동 개발자 디렉터리, SDK, 명령줄 도구, 프로젝트 의존성, 셸 환경이 함께 고정되어야 합니다.

먼저 작업 환경에서 실제 호출되는 Xcode를 확인합니다.

xcode-select -p
xcodebuild -version
xcrun --sdk iphoneos --show-sdk-path

앱 폴더에 Xcode가 여러 개 있어도 xcodebuild가 다른 개발자 디렉터리를 사용할 수 있습니다. 따라서 설치된 앱 목록만 확인하지 말고, 위 명령의 결과를 작업 로그에 남기되 개인 경로와 계정 정보는 삭제해야 합니다.

의존성 확인도 단계별로 나눕니다.

검증 대상 확인 방법 통과 의미
Xcode 선택 xcode-select, xcodebuild -version 예상한 개발자 디렉터리 사용
SDK xcrun --sdk iphoneos --show-sdk-path 대상 SDK 조회 가능
의존성 저장소의 잠금 파일 기준 설치 같은 커밋의 의존성 재현
컴파일 xcodebuild build 또는 프로젝트 스크립트 소스 컴파일 성공
Archive xcodebuild archive 배포 가능한 보관 파일 생성

Apple의 Xcode 배포 문서는 Archive와 배포 흐름을 별도 단계로 다룹니다. 따라서 의존성 해결 성공, 일반 컴파일 성공, Archive 성공을 하나의 “빌드 성공”으로 합치면 안 됩니다.

가장 강한 증거는 같은 커밋을 연속으로 실행하는 것입니다. 첫 번째 작업은 의존성 문제를 찾고, 두 번째 작업은 캐시와 환경이 바뀌어도 같은 도구 체인이 유지되는지 확인합니다.

SECTION 03작업 라우팅과 호스트 격리

프로젝트 수준 Runner와 태그를 사용하면 iOS 배포 작업을 지정된 원격 Mac으로 보낼 수 있습니다. GitLab Runner 태그와 보호된 작업 안내에 맞춰 배포용 태그를 만들고, 보호된 브랜치 작업만 해당 태그를 사용하도록 제한합니다.

예를 들어 태그는 다음처럼 명확하게 구분합니다.

ios_archive:
  tags:
    - macos-ios-release
  script:
    - ./ci/archive.sh

실제 태그 이름은 네 환경에 맞게 정하되, 저장소에 접근할 수 있는 모든 작업이 이 Runner를 사용할 수 있게 만들면 안 됩니다. Shell executor의 격리 한계에 따르면 작업 명령은 호스트 운영체제에서 직접 실행되며, 컨테이너 수준의 강한 격리를 자동으로 제공하지 않습니다.

다음 조건이면 전용 배포 Runner를 선택합니다.

  • 저장소에 외부 기여자의 스크립트가 들어올 수 있습니다.
  • 병합 요청에서 임의의 셸 명령이 실행됩니다.
  • 같은 사용자 계정에 서명 키와 운영 파일이 함께 있습니다.
  • 작업 디렉터리를 여러 프로젝트가 공유합니다.

이 중 하나라도 해당하면 일반 테스트 작업과 배포 작업을 같은 계정과 폴더에서 실행하지 않는 편이 안전합니다. 배포 Runner가 외부 병합 요청을 받지 않도록 보호된 브랜치와 실행 규칙을 함께 설정해야 합니다.

SECTION 04자격 증명 권한 구조

iOS 배포에서 자주 발생하는 오류는 App Store Connect 업로드 키를 전체 서명 자료로 착각하는 것입니다. 실제로는 다음 항목의 역할이 다릅니다.

자료 사용 목적 저장 및 사용 원칙
CI/CD 변수 작업에 필요한 값 전달 보호 범위와 마스킹 설정
App Store Connect API Key 업로드나 관리 API 인증 필요한 권한만 부여
인증서 앱 서명 전용 Keychain에 임시 import
개인 키 인증서와 함께 서명 평문 파일과 로그에 남기지 않음
Provisioning Profile 앱과 서명 조건 연결 대상 번들과 환경을 확인
Keychain 인증서와 개인 키 보관 작업 전 접근하고 작업 후 잠금

GitLab CI/CD 변수 문서의 보호 변수와 파일 형식 변수는 각각 다른 목적을 가집니다. 파일 형식 변수는 인증서나 프로파일을 임시 파일로 다루기 편하지만, 실행 권한을 가진 스크립트가 값을 읽지 못하게 만드는 기능은 아닙니다.

예시는 실제 자료가 아닌 자리 표시자로만 작성합니다.

CERT_FILE="$CI_PROJECT_DIR/tmp/<SIGNING_CERTIFICATE_FILE>"
PROFILE_FILE="$CI_PROJECT_DIR/tmp/<PROVISIONING_PROFILE_FILE>"
KEYCHAIN_FILE="$RUNNER_TEMP_DIR/<TEMP_KEYCHAIN_NAME>.keychain-db"

security create-keychain -p "<TEMP_KEYCHAIN_PASSWORD>" "$KEYCHAIN_FILE"
security unlock-keychain -p "<TEMP_KEYCHAIN_PASSWORD>" "$KEYCHAIN_FILE"
security import "$CERT_FILE" -k "$KEYCHAIN_FILE" -P "<CERTIFICATE_PASSWORD>" -T /usr/bin/codesign

<TEMP_KEYCHAIN_PASSWORD>, 인증서 이름, 팀 식별자, 앱 식별자, 키 식별자, 토큰은 실제 값으로 바꾸지 않은 상태를 문서와 예제에 유지해야 합니다. 작업이 끝나면 임시 파일을 삭제하고 Keychain을 잠그거나 제거합니다. App Store Connect API Key를 사용할 때는 Apple의 API Key 생성 문서에서 요구하는 역할 범위를 확인하고, 서명 인증서와 업로드 인증을 별도로 관리합니다.

SECTION 05캐시와 산출물 보존

캐시는 반복 작업의 의존성 다운로드를 줄이는 용도이고, Artifacts는 단계 사이에서 파일을 전달하거나 결과를 내려받는 용도입니다. GitLab의 캐시와 Artifacts 문서도 두 기능을 구분합니다.

잠금 파일이 바뀌면 캐시 키가 달라지도록 설계합니다.

cache:
  key:
    files:
      - <DEPENDENCY_LOCK_FILE>
  paths:
    - <DEPENDENCY_CACHE_PATH>

인증서, 개인 키, API 키, 유일한 배포용 Archive를 일반 캐시에 넣으면 안 됩니다. 캐시는 재사용 대상이고 접근 범위를 넓히기 쉬워서 비밀 자료의 보관 장소가 아닙니다.

반면 xcarchive, 서명된 내보내기 파일, xcresult는 역할을 분리해 기록합니다.

산출물 보관 목적 연결해야 할 정보
xcarchive Archive 재검토와 재내보내기 커밋, 작업 번호, Xcode 선택 결과
내보내기 파일 TestFlight 업로드 입력 내보내기 방식, 서명 조건
xcresult 테스트와 진단 결과 같은 작업의 Archive와 커밋

보존 기간과 용량은 프로젝트 설정값으로 명시해야 합니다. 공식 기본값처럼 단정하지 말고, 네 프로젝트에서 정한 값을 expire_in과 저장 정책에 반영한 뒤 팀 문서에 기록합니다.

SECTION 06실제 배포와 복구 판정

마지막 검증은 보호된 브랜치에서 수행합니다. 일반 Debug Build가 통과했다고 배포 환경이 준비된 것은 아닙니다.

실행 순서

  1. 격리된 테스트 프로젝트에서 Runner 태그와 사용자 계정을 확인합니다.
  2. 잠금 파일 기준으로 의존성을 설치하고 실제 Xcode 경로를 기록합니다.
  3. 테스트와 컴파일을 실행한 뒤 xcresult를 저장합니다.
  4. xcodebuild archivexcarchive를 생성합니다.
  5. 임시 Keychain과 Provisioning Profile을 사용해 배포 파일을 내보냅니다.
  6. App Store Connect에 업로드하고 빌드 처리가 시작되는지 확인합니다.
  7. 원격 Mac을 재시작한 뒤 Runner 온라인 상태와 다음 작업 수신을 확인합니다.
  8. 같은 커밋으로 Xcode 선택, Keychain 접근, Archive를 다시 실행합니다.

Apple은 App Store Connect 빌드 업로드 안내에서 업로드된 빌드와 처리 상태를 확인하는 흐름을 제공합니다. 업로드 명령이 종료 코드 0을 반환한 것만으로는 충분하지 않습니다. 관리 화면에서 빌드가 처리 대기나 처리 완료 상태로 넘어가는지 확인해야 합니다.

조건별 선택

  • 호스트 재시작 뒤 로그인 세션, Runner, Keychain, Archive, 업로드가 모두 통과하면 전용 원격 Mac을 상시 배포 Runner로 선택합니다.
  • Runner는 온라인이지만 Keychain이나 Archive가 실패하면 사용자 세션과 인증서 접근을 먼저 수정하고, 수정 전에는 배포 환경으로 사용하지 않습니다.
  • 외부 병합 요청이 같은 계정과 작업 폴더를 사용하면 테스트와 배포 Runner를 분리합니다.
  • 물리 기기 연결이나 직접적인 USB 접근이 필수이면 원격 Mac을 장기 배포 서버로 확정하지 말고 하드웨어 연결 조건을 먼저 검증합니다.
  • 상시 온라인 Mac이 없지만 배포 파이프라인을 검증해야 하면 원격 Mac에서 전용 Runner를 먼저 구성하고, 실제 작업 빈도에 따라 짧은 기간 또는 장기 운영을 결정합니다.

현재 사용 중인 Mac이 이 조건을 충족하는지 확인하려면 iOS 빌드 서버 운영 환경에 정리된 접근 방식을 참고할 수 있습니다. 별도 장비가 없다면 한국 지역 원격 Mac 이용 안내에서 테스트 기간과 접속 조건을 확인한 뒤, 먼저 실제 GitLab Pipeline으로 검증하는 편이 안전합니다.

수동으로 사용하는 개인 Mac은 대체로 빠르게 시작할 수 있지만, 재시작 후 로그인 상태가 달라지고 작업 폴더와 개발 파일이 섞이며 서명 키를 여러 용도로 공유하게 됩니다. 반대로 별도 Mac을 직접 구매하면 장기간 고정 부하에는 유리할 수 있지만 초기 비용, 운영체제 업데이트, 장애 대응, 상시 전원과 네트워크 관리가 네 책임이 됩니다. 이런 관리 부담을 피하면서 배포 빈도를 확인하려면 MACNOX 원격 Mac 요금과 기간을 비교해 보고, 먼저 짧은 기간에 재시작 복구까지 검증하는 방법이 현실적입니다.

SECTION 07자주 발생하는 판단 지점

FAQ에서 답한 것처럼 GitLab Runner가 온라인이라는 표시만으로는 충분하지 않습니다. 네 환경은 통과, 수정 필요, 전용 호스트 부적합 중 하나로 판정해야 합니다.

  • 통과: 전용 사용자 세션과 작업 폴더가 있고, 보호된 배포 작업만 수신하며, 실제 Archive와 업로드 및 재시작 후 재실행이 모두 성공합니다.
  • 수정 필요: Xcode 경로, 의존성 잠금, Keychain 접근, 변수 보호, 산출물 연결 중 하나가 불안정하지만 호스트를 분리하면 개선할 수 있습니다.
  • 공유 부적합: 외부 코드가 서명 키에 접근할 가능성이 있거나, 물리 기기 연결이 필요하거나, 여러 프로젝트가 같은 계정과 폴더를 통제 없이 사용합니다.

이 판정이 끝난 뒤에도 현재 Mac을 무조건 교체할 필요는 없습니다. 다만 상시 로그인 세션과 전용 계정이 없는 상태에서 배포 키를 먼저 넣는 것은 피해야 합니다. 필요한 기간에만 원격 Mac을 빌려 파이프라인을 검증하고, 반복 실행 빈도가 확인된 뒤 월간 또는 더 긴 운영 방식으로 넘어가는 순서가 리스크가 낮습니다.

SECTION 08자주 묻는 질문 FAQ

GitLab CI에서 iOS 빌드에 Mac Runner가 필요한 이유는 무엇인가요?

iOS 앱의 실제 빌드와 Archive에는 Xcode와 Apple SDK가 필요하며, 이 도구 체인은 macOS에서 실행됩니다. 따라서 Linux Runner가 저장소를 내려받고 일반 스크립트를 실행할 수 있어도 iOS 바이너리를 만드는 단계는 처리하지 못합니다. 전용 macOS Runner를 지정하고 Shell executor로 Xcode 명령을 실행해야 합니다.

Mac을 재시작한 뒤 GitLab Runner가 오프라인이면 어떻게 복구하나요?

먼저 지정된 macOS 사용자로 로그인되어 있는지 확인하고, 사용자 LaunchAgent가 실행 중인지 점검합니다. 그다음 Runner의 온라인 상태와 태그를 확인한 뒤 Xcode 선택 명령, Keychain 접근, 작업 디렉터리 권한을 순서대로 검사합니다. 자동 로그인만 유일한 복구책으로 삼지 말고 재시작 후 실제 작업을 다시 받아야 복구 완료로 판단합니다.

GitLab CI에서 iOS 인증서와 Provisioning Profile은 어떻게 넣나요?

인증서와 Provisioning Profile은 저장소 파일이나 일반 로그에 두지 말고 보호된 파일 형식 변수로 전달하는 방식이 적합합니다. 임시 경로에 파일을 만들고, 전용 Keychain을 생성하거나 잠금 해제한 뒤 필요한 작업이 끝나면 파일과 Keychain을 정리합니다. 변수 보호는 대상 브랜치와 실행 범위를 제한하지만 접근 권한 자체를 없애지는 않습니다.

GitLab Runner에서 Archive와 TestFlight 업로드를 자동화할 수 있나요?

가능합니다. 보호된 브랜치에서 의존성 확인, 테스트, Archive, 서명된 내보내기, App Store Connect 업로드를 서로 다른 단계로 실행할 수 있습니다. 업로드 성공만 보지 말고 App Store Connect에서 빌드 처리가 시작되고 상태가 갱신되는지 확인해야 합니다. 일반 Debug Build는 배포 검증을 대신할 수 없습니다.

원격 Mac에서 GitLab Runner를 실행할 때 그래픽 로그인 세션이 필요한가요?

Shell executor로 명령을 실행하는 데 화면 조작 자체가 항상 필요한 것은 아닙니다. 그러나 macOS의 사용자 LaunchAgent, 사용자 Keychain, 일부 Xcode 인증 흐름은 로그인한 사용자 세션과 연결됩니다. 따라서 원격 Mac을 로그아웃 상태로 방치하면 Runner는 온라인이어도 서명이나 Archive 단계에서 실패할 수 있습니다. 실제 배포 작업으로 세션 의존성을 확인해야 합니다.