결론: WWDC 26 이후 CI에서 Xcode 26 beta를 검증하려면 main workflow를 macos-latest로 바꿔 기도하는 방식이 아닙니다. 원격 Mac에서 Runner 두 레인으로 분리하는 것이 정답입니다. 프로덕션은 Xcode 16 안정판에 고정하고, beta는 전용 label과 별도 DerivedData 디렉터리로 돌립니다.
세 가지 철칙: ① 프로덕션 job에서 sudo xcode-select 금지; ② beta와 프로덕션의 -derivedDataPath는 물리적으로 분리; ③ iOS 27 Simulator runtime은 beta Runner에 한 번만 사전 설치하고 job마다 다운로드하지 않기.
글의 범위: 「WWDC 후 CI가 왜 느려지는가」( WWDC26 iOS CI 지연 글 )는 다루지 않습니다. 48시간 안에 실행 가능한 Runbook만 제공합니다.
6월 10일 글에서 말했듯 WWDC 이후 iOS CI의 충격은 캐시 초기화 + SDK 비대화 + 호스팅 Runner 혼잡이 겹친 결과입니다. 일주일이 지나 Slack 질문은 실무형으로 바뀌었습니다——「beta는 어느 Mac에 깔까?」「한 대에 Xcode 두 개?」「main이 빨갛게 되면 누구 책임?」. 답은 단순합니다: 원격 Mac mini를 self-hosted runner로 쓰고, beta 검증과 App Store 릴리스를 완전히 분리하세요. self-hosted가 없다면 먼저 iOS CI 가속 가이드를 읽고, 노드별 월 비용은 6개 지역 Runner TCO를 참고하세요.
1) 흔한 오해: Mac 한 대 혼용 ≠ 절약
WWDC 직후 가장 흔한 잘못된 결정은 CI Mac이 딱 한 대인 환경에 Xcode 16과 26 beta를 같이 설치하고, workflow의 xcode-select -s로 전환하는 것입니다. 월 한 대 요금처럼 보이지만 실제 비용은 이렇게 튑니다:
- 경합: job이 병렬로 돌 때 나중에 실행된
xcode-select가 전역 경로를 바꿔, 프로덕션 Archive가 beta 컴파일러를 쓰는 사고가 납니다. - 캐시 오염:
~/Library/Developer/Xcode/DerivedData를 공유하면 Swift module 버전이 섞여, 로컬 재현 불가한 「유령 버그」가 CI에서만 빨갛게 뜹니다. - 디스크 경쟁: Xcode 두 벌 + Simulator runtime 두 벌이면 80GB 이상은 흔합니다. 16GB 메모리 Mac은 beta 풀 빌드 중 swap으로 모든 job이 느려집니다.
2) 듀얼 Runner 아키텍처: label 풀과 workflow 분류
권장 최소 구성——원격 Mac mini M4 두 대(또는 프로덕션 1대 + beta 일일 임대 트라이얼):
| Runner | GitHub Label | Xcode 버전 | 담당 job |
|---|---|---|---|
| 프로덕션 A | self-hosted, macos, ios-prod | Xcode 16.4 (고정) | main Archive, TestFlight, 릴리스 tag |
| Beta B | self-hosted, macos, xcode26-beta | Xcode 26 beta | ios-27-* 브랜치, nightly 적응, API 탐색 |
workflow는 runs-on으로 하드 분류합니다. beta 브랜치가 ios-prod label을 타는 것은 금지:
name: iOS Production CI
on:
push:
branches: [main, release/*]
jobs:
archive:
runs-on: [self-hosted, macos, ios-prod]
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
DERIVED_DATA: /var/ci/deriveddata/prod
steps:
- uses: actions/checkout@v4
- name: Build & Archive
run: |
xcodebuild -scheme MyApp -configuration Release \
-derivedDataPath "$DERIVED_DATA" \
-archivePath build/MyApp.xcarchive archive
name: iOS 27 Beta Adapter
on:
push:
branches: [ios-27-*, feature/siri-ai-*]
schedule:
- cron: '0 2 * * *' # nightly, merge 차단 안 함
jobs:
beta-build:
runs-on: [self-hosted, macos, xcode26-beta]
continue-on-error: true # beta 실패가 main을 막지 않음
env:
DEVELOPER_DIR: /Applications/Xcode_26_beta.app/Contents/Developer
DERIVED_DATA: /var/ci/deriveddata/beta
steps:
- uses: actions/checkout@v4
- run: xcodebuild -scheme MyApp -sdk iphonesimulator build \
-derivedDataPath "$DERIVED_DATA"
Runner 등록과 label 명명은 GitHub self-hosted runner 문서를 참고하세요. label은 계약——label을 바꾼 사람이 책임집니다.
3) 원격 Mac에 Xcode 26 beta 설치와 다중 버전 공존
베어메탈 원격 Mac에서는 두 Xcode를 /Applications에 나란히 두고 디렉터리 이름으로 버전을 구분할 수 있습니다. beta가 안정판을 덮어쓰지 않게:
- Apple Developer Downloads에서 Xcode 26 beta(.xip)를 받아 SSH로 원격 Mac에 로그인한 뒤
/Applications/Xcode_26_beta.app에 풉니다. - 프로덕션 Mac은
DEVELOPER_DIR환경 변수만 받고, workflow에 절대 경로를 씁니다. job 내부sudo xcode-select -switch금지. - 최초 설치 후
xcodebuild -runFirstLaunch와 라이선스 동의를 수동으로 한 번 실행——매 CI job에 넣지 마세요. - iOS 27 Simulator runtime은 beta Mac에 사전 설치: Xcode → Settings → Platforms에서 일괄 설치; workflow에서
-downloadPlatform단계 삭제.
로컬 개발에서도 여러 Xcode를 쓰는 팀은 Xcode 빌드 지연 해결의 하이브리드 워크플로를 참고하세요. 로컬에서 Swift 작성, 무거운 컴파일은 클라우드 M4에——beta 적응도 동일하고, 16GB 노트북에 beta 인덱싱과 풀 빌드를 얹지 마세요.
4) DerivedData / Pods / SPM 3종 캐시 격리
WWDC 시즌 캐시가 죽는 근본 원인은 컴파일러와 module 형식 변경이지 「캐시가 고장」이 아닙니다. 격리 전략:
| 캐시 유형 | 프로덕션 경로 | Beta 경로 | 비고 |
|---|---|---|---|
| DerivedData | /var/ci/deriveddata/prod | /var/ci/deriveddata/beta | workflow에 -derivedDataPath 고정 |
| CocoaPods | /var/ci/cocoapods/prod | /var/ci/cocoapods/beta | CP_HOME_DIR 또는 --deployment |
| SPM | /var/ci/spm/prod | /var/ci/spm/beta | clonedSourcePackagesDirPath |
| ModuleCache | DerivedData 부모 디렉터리 종속 | 독립 beta 트리 | 기본 ~/Library 공유 금지 |
actions/cache는 WWDC 시즌에 부족한 경우가 많습니다: Xcode 메이저 전환 시 cache key가 전부 무효화되고, 수 GB DerivedData 업로드 네트워크 시간이 로컬 디스크보다 손해일 때가 많습니다. self-hosted의 가치는 디스크가 job을 넘어 살아남는 것——두 번째 beta build에서야 20분대에서 10분대로 내려갑니다. Flutter 3종 캐시는 Flutter iOS CI, 서명과 TestFlight는 iOS CI 가속 가이드 Signing 절을 보세요.
sudo mkdir -p /var/ci/{deriveddata,cocoapods,spm}/{prod,beta}
sudo chown -R $(whoami) /var/ci
# 디스크 모니터: DerivedData 팽창 시 lane별 정리, 디스크 전체 rm 금지
du -sh /var/ci/deriveddata/*
5) 전형적 함정: beta 크래시, SDK 비대, 키체인 혼선
현장에서 자주 보는 사고와 예방책:
- beta 빨강이 merge를 막음: beta workflow에
continue-on-error: true, required status check로 두지 않기. beta 불안정은 예상 범위이지 엔지니어 탓이 아닙니다. - 프로덕션이 beta SDK 사용: Archive 로그에서
DTXcode와DEVELOPER_DIR확인; prod job 시작에test "$DEVELOPER_DIR" = "/Applications/Xcode_16.4.app/Contents/Developer"어설션. - 키체인 / Match 인증서 혼선: Runner 두 대는 각각 독립 login keychain; Fastlane match
git_url은 공유 가능하나 인증서 import keychain 비밀번호는 기기별 분리. Apple Xcode 지원 매트릭스 참고. - 디스크 풀: beta DerivedData + Simulator runtime 두 벌은 급증; beta Mac 512GB SSD 권장,
beta트리에서 14일 이상 미접근 subfolder 주간 cron 삭제. - 병렬 Archive OOM: M4 16GB에서 Archive 2개 동시는 swap; 프로덕션 concurrency 1, beta 1–2 조정.
6) 샘플: Runner 분리 전후 동일 저장소 P50 (SLA 아님)
Swift/UIKit + CocoaPods 중간 규모, main 프로덕션 job WWDC 후 1주차 비교——미분리(전원 beta 업그레이드) vs 듀얼 Runner(프로덕션 Xcode 16 유지):
| 지표 | 미분리 (main beta화) | 듀얼 Runner 격리 |
|---|---|---|
| main Archive P50 | ~42 min | ~11 min (WWDC 전과 유사) |
| beta 적응 job P50 | (prod와 한 대 경쟁) | ~22 min (1주차 풀, 2주차 ~12 min) |
| main beta 오인 실패율 | 높음 (컴파일러/캐시 혼용) | 거의 0 |
| 필요 Mac 수 | 1대 (겉보기 절약) | M4 2대 또는 프로덕션 1 + beta 일일 |
수치는 저장소마다 다릅니다. 자사 workflow로 48시간 A/B를 돌리세요. 핵심은 「얼마나 빨라지나」가 아니라 main이 beta 대금을 내지 않는다는 점입니다.
7) 48시간 롤아웃 체크리스트
- Day 0 오전: 프로덕션 Xcode 버전 잠금, prod workflow에
DEVELOPER_DIR고정; main에 「Xcode 26 업그레이드」 CI 변경 merge 금지. - Day 0 오후: 원격 Mac mini(또는 48h 일일 임대)를
xcode26-betaRunner로 등록; Xcode 26 beta + iOS 27 runtime 설치. - Day 1:
/var/ci/...캐시 디렉터리 생성; beta workflow 첫 풀 빌드 통과; prod job 영향 없음 확인. - Day 2: beta workflow를 nightly +
ios-27-*브랜치 트리거로 변경; GitHub branch protection에서 beta check required 제거. - 검수: main Archive 3회 연속 < 15 min (프로젝트별); beta job 실패 시 Slack @channel 금지.
두 번째 Mac 월 임대가 맞는지 불확실하면, 일일 beta Mac으로 iOS 27 적응 spike를 끝낸 뒤 본계약 판단——TCO 템플릿은 MacBook Pro vs 클라우드 Mac 결정. 툴체인이 macOS 전용인 이유는 Xcode 툴체인 macOS 전용 글 참고.
8) 자주 묻는 질문 (15개)
1. 원격 Mac 한 대에 Xcode 두 개? 가능. 다른 .app 이름으로 공존; CI에서는 DEVELOPER_DIR 지정, 전역 xcode-select 의존 금지.
2. beta Runner 24GB 필수? 단일 scheme 중간 규모면 16GB M4로 충분한 경우 많음; 다중 target 병렬 Archive는 24GB 권장.
3. 프로덕션 Mac에서 가끔 beta job? 비권장. 시간 분할해도 DerivedData는 최소 분리; 가능하면 물리 분리.
4. 호스팅 macos-latest가 beta Runner 대체? 아님. ephemeral 디스크는 매번 콜드 스타트, WWDC 시즌 bootstrap만 15분+, 영속 캐시 없음.
5. actions/cache로 충분? 메이저 전환 시 key 무효; 큰 DerivedData up/down은 로컬 디스크보다 불리.
6. beta 불안정으로 CI 전체 빨강? 예상 범위; beta lane optional, main merge 차단 금지.
7. prod가 beta 컴파일러 안 쓰는지 확인? Archive 로그 DTXcode 또는 job 시작 DEVELOPER_DIR 어설션.
8. Simulator runtime 매 job 다운로드? 하지 않음. beta Mac 1회 사전 설치, workflow download 삭제.
9. Runner 두 대 다른 리전? 가능. 프로덕션은 팀 가까운 노드; beta는 prod와 동일 리전이 ops 단순.
10. OpenClaw로 beta Runner 관리? webhook 트리거 가능; Gateway는 생략. CI 등록은 OpenClaw CI Runner FAQ.
11. 일일 beta Mac으로 충분? 적응 spike는 충분; nightly 장기 운영은 월 임대가 유리.
12. DerivedData 몇 GB에서 정리? lane당 40GB 초과 시 scheme별 정리; 프로덕션 lane 신중, beta 우선 삭제.
13. Swift 6 동시성 검사 더 엄격하면? beta lane만 strict 플래그, prod는 현 설정 유지하며 단계 이전.
14. WWDC 글과 관계? 그 글은 「왜 느린가」, 이 글은 「Runner 어떻게 나누나」.
15. 48h 안에 2대 필수? main이 beta 안 건드리면 prod 1대로 OK; beta 적응은 일일 Mac 도착 후 lane 오픈.
Beta는 실험, 프로덕션은 반드시 안정
WWDC 후 가장 비용 대비 좋은 구성은 월 임대 프로덕션 Runner 1대 + 온디맨드 beta Mac입니다. 프로덕션 Mac mini M4를 7×24로 TestFlight 페이스를 지키고, beta는 일일 임대로 iOS 27 적응을 시험한 뒤 본계약 여부를 결정하세요. Nuvcloud 베어메탈은 디스크 독점, DerivedData가 job을 넘어 유지; M4 16GB면 대부분 iOS Archive 충분, 24GB는 다중 scheme 병렬용. 저전력·팬리스로 무인 CI에 적합합니다.
WWDC 후 Xcode 26 beta CI를 설계 중인데 main을 실험장으로 쓰고 싶지 않다면, Nuvcloud 클라우드 Mac mini M4가 가장 저렴한 분리 출발점입니다—— 요금제 바로 확인 , 48시간 안에 beta / 프로덕션 Runner를 나누세요.