1–5분 배포

Runner 대기 없이 iOS 빌드 바로 실행

$21.8 /일부터 · 물리 서버 전용
클라우드 Mac 구성
M4 · 16 GB Xcode 버전 고정 가능 전 세계 5개 노드

로컬 Mac 없이 iOS CI/CD 구축: GitHub Actions 셀프 호스팅 Runner 연결

GitHub 호스팅 macOS Runner는 피크 시간 대기가 길고 Xcode 버전도 고정하기 어렵습니다. VPSRox Mac mini M4 클라우드 렌탈 노드에서 Xcode 빌드, Archive 서명, TestFlight 업로드를 구성하는 방법을 단계별로 정리합니다. GitHub Actions 셀프 호스팅 Runner 설치, 키체인 문제 해결, Jenkins Agent 비교, SwiftUI 프로젝트 실측 소요 시간을 포함합니다.

셀프 호스팅 Runner가 GitHub 호스팅 Runner보다 iOS 빌드에 더 적합한 이유는?

GitHub Actions의 macOS 호스팅 Runner는 서버 관리 부담을 줄여 주지만, iOS CI/CD를 실제로 돌리면 세 가지 병목이 반복됩니다. 팀마다 겪는 패턴이 비슷해서, 셀프 호스팅 Runner로 전환하는 경우가 많습니다.

첫 번째는 큐 대기입니다. GitHub macOS Runner 풀은 Linux 대비 훨씬 작습니다. 피크 시간대(UTC 12:00–20:00, 한국·일본 업무 시간)에는 runs-on: macos-latest job이 Runner에 잡히기까지 중앙값 8–15분, 간헐적으로 20분 이상 걸립니다. Clean Build가 6분이면 되는 중형 SwiftUI 앱도, 대기 시간이 컴파일보다 길어지는 날이 흔합니다.

두 번째는 Xcode 버전 고정 불가입니다. macos-latest는 GitHub 인프라 업그레이드와 함께 Xcode가 조용히 바뀝니다. Xcode 15에서 16으로 넘어갈 때 사전 공지 없이 빌드가 깨진 사례가 많고, workflow에 xcode-select 단계를 추가하거나 메이저 버전 호환 작업을 기다려야 했습니다.

세 번째는 무료 분 소진 속도입니다. macOS Runner는 Linux 대비 10배 과금(GitHub 공식 문서)이라, 무료 플랜 월 2,000분은 macOS 기준 약 200분입니다. 스프린트마다 PR 빌드가 몇 번만 돌아도 초과 요금 구간에 들어갑니다.

셀프 호스팅 Runner를 베어메탈 Mac에 붙이면 위 세 가지를 한 번에 해소할 수 있습니다. Runner는 상시 온라인, Xcode 버전은 직접 고정, 실행 시간은 GitHub 과금 대상이 아닙니다. 남은 과제는 24시간 켜 둘 macOS 호스트를 확보하는 일—이 글은 그 부분을 다룹니다.

8–20 min
GitHub 호스팅 macOS Runner 피크 대기 시간
< 30 s
셀프 호스팅 Runner(온라인 Mac) job 시작 지연
10×
macOS 대 Linux 과금 배율(GitHub 공식)
고정
셀프 호스팅 노드 Xcode 버전 완전 통제

준비 작업: 클라우드 Mac 노드 개통 및 기본 환경

iOS CI/CD 파이프라인을 연결하려면 SSH로 접속 가능하고 24시간 가동되는 macOS 호스트가 필요합니다. 이 가이드는 VPSRox Mac mini M4 베어메탈 노드를 기준으로 설명합니다. 사무실 Mac mini나 Mac Studio에도 동일하게 적용되며, VPSRox 콘솔에서 배포 후 1–5분 안에 아래 단계를 진행할 수 있습니다.

SSH 접속 및 Xcode Command Line Tools

  1. 01
    SSH 키 인증 구성

    콘솔에서 공인 IP를 확인한 뒤 로컬 터미널에서 실행합니다.

    ssh-copy-id -i ~/.ssh/id_ed25519.pub user@<노드IP>

    설정이 끝나면 비밀번호 로그인을 끄고(PasswordAuthentication no) 무차별 대입 공격을 막으세요.

  2. 02
    Xcode Command Line Tools 설치

    xcode-select --install

    Archive 서명까지 자동화하려면 전체 Xcode가 필요합니다. developer.apple.com/downloads에서 .xip를 받아 압축 해제한 뒤 /Applications/로 옮기고, 활성 경로를 전환합니다.

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

  3. 03
    Homebrew 설치

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

    설치 스크립트가 안내하는 대로 /opt/homebrew/bin을 PATH에 넣고 ~/.zprofile에 저장하세요.

  4. 04
    Xcode 빌드 체인 검증

    xcodebuild -version

    출력된 Xcode 버전과 Build 번호를 기록해 두세요. 이후 workflow YAML에서 XCODE_VERSION 환경 변수로 동일 버전을 고정할 수 있습니다.

권장: xcodes로 다중 Xcode 버전 관리

brew install xcodesorg/made/xcodesxcodes를 설치하면 Xcode 전환이 빠르고, xcode-select 경로를 매번 손으로 바꿀 필요가 없습니다. CI workflow에서 xcodes select 16.2처럼 버전을 지정하면 Runner 업그레이드로 인한 드리프트를 막을 수 있습니다.

GitHub Actions 셀프 호스팅 Runner 설치 및 등록

GitHub가 배포하는 셀프 호스팅 Runner 패키지는 Mac에서 폴링 데몬을 띄웁니다. workflow가 트리거되면 job을 내려받아 로컬에서 실행하므로, Mac 쪽에 포트를 열 필요가 없습니다.

GitHub 저장소에서 등록 토큰 발급

대상 저장소 → SettingsActionsRunnersNew self-hosted runner에서 macOS를 선택합니다. 페이지에 나온 다운로드 명령과 등록 토큰을 복사하세요. 토큰은 발급 후 1시간만 유효합니다.

  1. 01
    Runner 다운로드 및 압축 해제

    mkdir actions-runner && cd actions-runner

    curl -o actions-runner-osx-arm64-2.319.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-osx-arm64-2.319.1.tar.gz

    tar xzf ./actions-runner-osx-arm64-2.319.1.tar.gz

    osx-arm64 패키지(Apple Silicon)를 선택하세요. 버전 번호는 GitHub 페이지에 표시된 최신 릴리스를 따릅니다.

  2. 02
    Runner 등록

    ./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token <TOKEN> --name mac-m4-vpsrox --labels mac,xcode16,ios

    --labels로 붙인 사용자 정의 라벨은 YAML의 runs-on에서 사용해, 이 Mac으로 job을 정확히 라우팅할 수 있습니다.

  3. 03
    launchd 서비스로 설치(부팅 시 자동 시작)

    ./svc.sh install

    ./svc.sh start

    launchd가 로그인 후 Runner 프로세스를 자동으로 기동합니다. 상태 확인: ./svc.sh status

Workflow YAML에서 셀프 호스팅 Runner 지정

등록이 끝나면 .github/workflows/ios-ci.ymlruns-on을 사용자 정의 라벨로 바꿉니다.

jobs:
  build:
    runs-on: [self-hosted, mac, xcode16]
    steps:
      - uses: actions/checkout@v4
      - name: Show Xcode version
        run: xcodebuild -version
보안 경고: 공개 저장소에서 셀프 호스팅 Runner 주의

공개 저장소의 셀프 호스팅 Runner는 fork PR workflow까지 실행될 수 있어, 악성 코드가 Mac에서 돌아갈 위험이 있습니다. 권장 사항: ① 비공개 저장소 또는 Organization 범위에서만 사용; ② Runner를 비관리자 시스템 계정으로 실행하고 권한을 제한; ③ ~/.bash_history, ~/actions-runner/_diag/ 로그를 정기적으로 검토.

Xcode 빌드 환경 및 의존성 관리 구성

안정적인 iOS CI/CD 파이프라인을 만들려면 보통 세 가지를 정리해야 합니다. 의존성 가져오기, 빌드 캐시, DerivedData 경로 격리입니다. 셀프 호스팅 Runner는 매 job마다 깨끗한 VM이 아니므로, 증분 빌드와 캐시 전략이 특히 중요합니다.

CocoaPods vs Swift Package Manager

프로젝트가 CocoaPods를 쓰면 Runner Mac에 한 번만 설치하면 됩니다.

sudo gem install cocoapods

CI 단계 예시:

- name: Install CocoaPods
  run: pod install --repo-update
  working-directory: ./ios

Swift Package Manager는 Xcode가 첫 빌드 때 의존성을 해석합니다. 이후 job 속도를 올리려면 SPM 해석 결과를 캐시하세요.

- uses: actions/cache@v4
  with:
    path: ~/Library/Developer/Xcode/DerivedData
    key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}

DerivedData 경로 격리

한 Runner에서 여러 브랜치나 프로젝트가 동시에 빌드되면 기본 DerivedData 경로가 충돌합니다. xcodebuild 호출마다 경로를 명시하는 편이 안전합니다.

xcodebuild \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -sdk iphoneos \
  -configuration Release \
  -derivedDataPath ./DerivedData \
  clean build

병렬 빌드 파라미터 조정

Mac mini M4는 10코어 CPU(4 성능 코어 + 6 효율 코어)를 탑재했습니다. Xcode 기본 병렬 컴파일 수는 보통 코어 수와 같습니다. 대형 모듈이 많은 프로젝트는 메모리 부담을 줄이기 위해 값을 낮출 수 있습니다.

defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 6

Swift 파일 약 80개 규모의 SwiftUI 앱을 16 GB RAM M4 베어메탈 노드에서 측정했을 때, Clean Build는 약 4분 12초, swap 없이 완료되었습니다. 같은 Runner에서 증분 빌드는 1분 내외로 떨어지는 경우가 많습니다.

코드 서명 및 키체인: Archive 패키징 전체 프로세스

코드 서명은 iOS CI/CD에서 가장 자주 막히는 구간입니다. GUI 없는 CI 환경에서는 macOS Keychain 동작이 로컬 개발 Mac과 달라지므로, 키체인을 workflow 안에서 명시적으로 만들고 해제해야 합니다.

두 가지 주요 서명 방식 비교

방안 적용 시나리오 장점 단점
p12 인증서 수동 가져오기 소규모 팀 단일 머신 CI 구성 간단, 외부 의존성 불필요 인증서 순환 시 수동 재가져오기 필요
fastlane match 팀 협업 / 다중 CI 노드 인증서 통합 암호화 저장, 자동 순환 Git 저장소 또는 S3 스토리지 백엔드 필요

수동 p12 방식: job마다 임시 키체인 생성

CI에서 가장 안정적인 패턴은 job 시작 시 임시 키체인을 만들고, Archive가 끝나면 삭제하는 것입니다. 이전 job의 인증서가 남지 않습니다.

# Create a temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain

# Import signing certificate
security import "$CERTIFICATE_P12_PATH" \
  -k build.keychain \
  -P "$P12_PASSWORD" \
  -T /usr/bin/codesign \
  -T /usr/bin/xcodebuild

# Allow xcodebuild to access the key without UI prompt
security set-key-partition-list \
  -S apple-tool:,apple: \
  -s -k "$KEYCHAIN_PASSWORD" build.keychain

KEYCHAIN_PASSWORD, P12_PASSWORD 같은 민감 값은 GitHub Repository Secrets에 넣고 ${{ secrets.KEYCHAIN_PASSWORD }}로 참조하세요. YAML에 직접 적으면 안 됩니다.

Archive 실행 및 IPA 내보내기

Archive → Export는 두 단계로 나뉩니다. 먼저 xcodebuild archive.xcarchive를 만들고, 이어서 -exportArchive로 IPA를 뽑습니다.

# Step 1: Archive
xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -sdk iphoneos \
  -archivePath ./build/MyApp.xcarchive \
  CODE_SIGN_IDENTITY="Apple Distribution: Your Name (XXXXXXXX)" \
  DEVELOPMENT_TEAM="XXXXXXXX"

# Step 2: Export IPA
xcodebuild -exportArchive \
  -archivePath ./build/MyApp.xcarchive \
  -exportOptionsPlist ExportOptions.plist \
  -exportPath ./build/

ExportOptions.plist는 Export 단계의 핵심 설정 파일입니다. method(app-store / ad-hoc), teamID, signingStyle 필드가 필요합니다. Xcode GUI에서 한 번 Export한 뒤 임시 폴더에 생성된 파일을 템플릿으로 쓰면 편합니다.

fastlane으로 TestFlight 자동 업로드 연동

IPA가 준비되면 마지막 단계는 App Store Connect TestFlight 채널로 올리는 일입니다. fastlane pilot(upload_to_testflight)은 App Store Connect API Key 인증을 지원해, Apple ID 비밀번호 없이도 안정적으로 업로드할 수 있습니다.

fastlane 설치

버전 고정을 위해 Bundler 사용을 권장합니다. 전역 gem 충돌을 피할 수 있습니다.

# In project root, create Gemfile
source "https://rubygems.org"
gem "fastlane"

이후 bundle install을 실행하고, fastlane 명령은 모두 bundle exec fastlane ... 형태로 호출하세요.

App Store Connect API Key 구성

App Store Connect → Users and AccessIntegrationsApp Store Connect API에서 API Key를 만들고 .p8 파일을 내려받습니다. Key ID와 Issuer ID도 함께 기록하세요.

GitHub Secrets에 아래 변수를 등록합니다.

  • ASC_API_KEY_ID: API Key ID
  • ASC_API_ISSUER_ID: Issuer ID
  • ASC_API_KEY_CONTENT: .p8 파일 내용의 Base64 인코딩

Fastfile 구성 예시

lane :beta do
  api_key = app_store_connect_api_key(
    key_id: ENV["ASC_API_KEY_ID"],
    issuer_id: ENV["ASC_API_ISSUER_ID"],
    key_content: Base64.decode64(ENV["ASC_API_KEY_CONTENT"])
  )

  upload_to_testflight(
    api_key: api_key,
    ipa: "./build/MyApp.ipa",
    skip_waiting_for_build_processing: true
  )
end

GitHub Actions YAML에서 호출 예시:

- name: Upload to TestFlight
  env:
    ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
    ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }}
    ASC_API_KEY_CONTENT: ${{ secrets.ASC_API_KEY_CONTENT }}
  run: bundle exec fastlane beta
가속 팁: App Store 처리 대기 건너뛰기

skip_waiting_for_build_processing: true를 쓰면 fastlane이 업로드 직후 종료하고, App Store Connect의 IPA 처리(보통 10–30분)를 기다리지 않습니다. 후속 단계가 처리 완료에 의존하지 않는다면(예: Slack 빌드 번호 알림) iOS CI/CD 전체 소요 시간을 크게 줄일 수 있습니다.

키체인 일반 오류 집중 해결

코드 서명 오류 메시지는 종종 모호합니다. 헤드리스 CI에서 자주 보는 증상, 원인, 해결책을 표로 정리했습니다.

오류 메시지 근본 원인 해결 방법
errSecItemNotFound 인증서가 활성 키체인에 없거나, 임시 키체인이 GC됨 security default-keychain -s build.keychain 실행 여부 확인; archive 전에 security unlock-keychain 추가
could not find signing certificate for "Apple Distribution" 인증서 Identity와 Xcode CODE_SIGN_IDENTITY 불일치 security find-identity -v -p codesigning build.keychain으로 전체 Identity 문자열을 확인해 그대로 복사
User interaction is not allowed 키체인 잠금 상태 — codesign이 UI 승인 대화상자를 요구(헤드리스 환경 불가) security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain 추가로 UI 없이 개인키 접근 허용
Provisioning profile doesn't include the entitlement 프로비저닝 프로파일과 Entitlements 불일치(Push, App Groups 등) Apple Developer에서 프로파일 재생성, Capability 선택 확인 후 재다운로드·설치
No signing certificate "iOS Distribution" found 프로비저닝 프로파일과 인증서 유형 불일치 (Distribution vs Development) ExportOptions.plist에서 method 필드를 확인하세요 (app-store는 Distribution 인증서 필요)

디버깅 팁: 빌드 로그에서 CodeSign 명령 확인

xcodebuild 뒤에 | xcpretty 파이프를 붙이면 로그가 정리되지만, 서명 디버깅에는 원본 로그가 더 유용합니다. 파이프를 제거하거나 CodeSign 키워드로 검색하면 실제 서명 인자를 확인할 수 있습니다. Xcode GUI 오류 패널보다 정보가 훨씬 많습니다.

추가로 자주 쓰는 검증 명령:

codesign -dv --verbose=4 ./build/MyApp.xcarchive/Products/Applications/MyApp.app

.app 번들 안의 서명 정보를 직접 읽어 인증서 체인이 완전한지 확인합니다.

Jenkins Agent 비교: 마이그레이션 및 트레이드오프 분석

이미 Jenkins를 운영 중이거나, 사내 정책상 코드를 GitHub에 올릴 수 없다면 macOS Jenkins Agent도 흔한 선택입니다. GitHub Actions 셀프 호스팅 Runner와 비교할 때 실무에서 체감되는 차이는 아래 표와 같습니다.

비교 항목 GitHub Actions 셀프 호스팅 Runner Jenkins Agent (macOS)
구성 난이도 낮음, 5분 이내 등록 완료 중간, Jenkins master 구축 필요 (보통 Linux/Docker)
코드 저장소 연동 GitHub 네이티브, PR 검사 상태 자동 반영 GitHub Branch Source 플러그인 구성 필요
멀티 플랫폼 혼합 동일 workflow에서 macOS / Linux / Windows 혼합 사용 가능 Label 라우팅 필요, 구성이 상대적으로 번거로움
파이프라인 시각화 GitHub Actions 네이티브 UI, 직관적 Blue Ocean 플러그인 효과 좋으나 자체 유지보수 필요
시크릿 관리 GitHub Secrets + OIDC Jenkins Credentials, Vault 연동 가능
오프라인/내부망 빌드 미지원 (GitHub 접근 필요) 지원, 강력한 컴플라이언스 시나리오에 적합
하드웨어 요구사항 온라인 macOS 머신 1대 필요 (Runner 호스트) 온라인 macOS 머신 1대 필요 (Jenkins Agent 호스트)

Jenkins에서 넘어올 때 sh 'xcodebuild ...' 단계는 GitHub Actions YAML의 run: 필드로 거의 그대로 옮길 수 있습니다. 차이가 큰 부분은 트리거 문법동시 실행 제어입니다. Jenkins when { branch ... } ↔ GitHub Actions on: push: branches:; Jenkins lockable-resources ↔ GitHub Actions concurrency: 그룹.

두 방식 모두 공통 전제는 상시 가동·고정 macOS 버전의 베어메탈 Mac 1대가 Agent/Runner 호스트로 필요하다는 점입니다. 이 Mac이 파이프라인 안정성의 기반이며, 발열·버전 드리프트가 생기면 CI 실패가 예측하기 어려워집니다.

많은 중소 팀이 막히는 지점은 도구 선택이 아니라 장기간 CI를 돌릴 Mac이 없다는 사실입니다. 사무용 MacBook은 8 GB RAM인 경우가 많고, 대형 Xcode 빌드에서 발열·클럭 다운이 심합니다. Mac mini를 새로 사면 초기 비용에 전력·공간·유지보수까지 더해집니다.

클라우드 전용 Mac: 전용 물리 서버가 없을 때의 고정 빌드 솔루션

전용 Mac이 없을 때 Mac mini 클라우드 렌탈은 현실적인 대안입니다. 초기 하드웨어 투자 없이도 베어메탈 Mac mini M4 위에 Xcode 버전을 고정한 iOS CI/CD 빌드 노드를 바로 쓸 수 있습니다.

이 가이드에서 사용한 VPSRox Mac mini M4 베어메탈 노드 기준: 16 GB 통합 메모리, 256 GB NVMe SSD, 1 Gbps 전용 대역폭, 일 $21.8부터, 결제 후 1–5분 내 자동 배포, 장기 계약 없음, SLA 99.9%.

CI/CD 관점에서는 상시 온라인인 전용 Mac mini를 렌탈하는 것과 같습니다. 로컬 물리 서버와 동일하게 아래 작업이 가능합니다.

  • xcodes로 원하는 Xcode 버전 설치·빌드 환경 완전 고정
  • GitHub Actions 셀프 호스팅 Runner 또는 Jenkins Agent로 등록 — 이 글의 단계를 그대로 적용
  • M4 10코어 CPU·38 TOPS Neural Engine으로 Core ML 모델 컴파일 프로젝트에 유리
  • Thunderbolt 5 병렬 확장으로 여러 Mac mini를 사설 빌드 클러스터로 구성(대형 팀)

GitHub 호스팅 Runner와 비교하면, 하루 30회 이상 빌드하는 팀은 일 단위 렌탈 비용이 macOS Runner 초과 과금보다 낮은 경우가 많습니다. Mac mini 구매와 비교하면, 스프린트 기간만 노드를 늘리고 출시 후 줄이는 식으로 렌탈 기간을 조절할 수 있어 초기 CAPEX 부담이 없습니다.

싱가포르, 일본(도쿄), 한국(서울), 홍콩, 미국 동부 — 전 세계 5개 노드 중 팀과 가까운 위치를 고르면 git fetch와 의존성 다운로드 지연이 줄어 전체 빌드 시간을 더 단축할 수 있습니다. 한국(서울) 노드는 국내 팀의 iOS CI/CD latency에 특히 유리합니다.

한 대로 여러 용도: CI/CD + 원격 Mac 개발 환경

클라우드 Mac 노드는 Runner 전용일 필요가 없습니다. 브라우저 VNC나 SSH로 원격 Mac 데스크톱처럼 쓰면서, 같은 머신에서 Xcode 디버깅·Instruments 분석·시뮬레이터 테스트까지 할 수 있습니다. 노드 활용률이 올라가면 일 단위 렌탈 단가 체감 비용도 낮아집니다.

물리 머신 전용 · 1–5분 내 배포

이 CI/CD 파이프라인을 가동하기

이 글의 모든 단계는 VPSRox Mac mini M4 베어메탈 노드에서 검증했습니다. 노드 개통 → SSH 접속 → 가이드대로 설정하면, 당일 안에 TestFlight 자동 업로드까지 iOS CI/CD 파이프라인을 완성할 수 있습니다. 일 단위 렌탈, 장기 계약 불필요.

표준 구성
Apple M4 · 38 TOPS
CPU10코어(4P + 6E)
메모리16 GB 통합 메모리
네트워크1 Gbps 전용 대역폭
SLA99.9% 가용성
배포1–5분 자동 개통