연결부터 첫 빌드 산출물까지

Mac을 받은 뒤
첫 한 시간

처음 30분은 연결, 툴체인 점검, 첫 컴파일에 쓰고, 나머지 30분은 서명과 파이프라인을 정리하세요. 가상 머신이 아닌 전용 물리 Mac이므로 기존 macOS 그래픽 인터페이스와 명령줄 워크플로를 그대로 사용할 수 있습니다.

이미 주문하셨나요? 콘솔에서 실제 호스트 주소, 포트, 사용자 이름과 제공 정보를 확인하세요. 예시 변수를 접속 자격 증명으로 사용하지 마세요.

00~10분

호스트 지문을 확인하고 SSH로 연결합니다.

10~20분

Xcode, 프로젝트 의존성, 디스크 공간을 확인합니다.

20~30분

서명 없이 점검을 먼저 실행한 다음 archive를 확인합니다.

1단계 · 연결부터 확인

명령은 SSH로, GUI는 화면 공유로

먼저 로컬에서 키를 생성하고 공개 키만 업로드하세요. 개인 키는 암호를 설정해 본인 기기에 보관합니다. 아래 명령의 호스트, 포트, 사용자 이름은 콘솔에 표시된 실제 정보를 사용하세요.

로컬 macOS 터미널에서 실행
read -r -p "호스트 주소: " MAC_HOST
read -r -p "SSH 포트: " MAC_PORT
read -r -p "사용자 이름: " MAC_USER
ssh-keygen -t ed25519 -f "$HOME/.ssh/osxhost_build"
cat "$HOME/.ssh/osxhost_build.pub"
ssh -p "$MAC_PORT" "$MAC_USER@$MAC_HOST"

공개 키 파일은 .pub 로 끝납니다. 제공된 로그인 방식으로 처음 연결한 뒤 공개 키를 원격 서버의 ~/.ssh/authorized_keys에 추가하고, 다른 로컬 터미널에서 키 로그인을 확인하세요.

원격 서버에 공개 키를 추가하고 비밀번호 변경하기

원격 서버에서 ~/.ssh디렉터리를 만들고 권한을 700으로 설정하세요. 공개 키 파일의 권한은 600으로 설정합니다. 편집기로 기존 승인 항목을 유지하면서 공개 키를 추가한 뒤 passwd 를 실행해 현재 사용자의 비밀번호를 변경하세요.

새 터미널을 열고 다음 명령으로 ssh -i ~/.ssh/osxhost_build -p "$MAC_PORT" "$MAC_USER@$MAC_HOST" 로그인되는지 확인한 뒤 기존 세션을 종료하세요. 사용할 수 있는 유일한 연결을 먼저 닫지 마세요.

확인: 새 세션에서 키로 로그인 가능

암호화 터널을 통해 화면 공유 사용

로컬에서 아래 터널 명령을 실행하고 터미널을 계속 열어 두세요. macOS에서는 ‘화면 공유’를 열고 vnc://127.0.0.1:5901에 연결하세요. Windows와 Linux에서는 해당 VNC 인증 방식을 지원하는 클라이언트를 사용해 로컬 포트 5901에 연결하세요.

원격 화면 공유가 활성화되어 있고 현재 사용자에게 권한이 부여되어 있어야 합니다. GUI 로그인 정보와 SSH 개인 키는 별도의 인증 수단입니다. VNC 포트를 모든 주소에서 접속할 수 있도록 직접 공개하지 마세요.

확인: macOS 데스크톱을 보고 조작할 수 있음
로컬 SSH 터널 · 원격 루프백 주소로 연결
ssh -N -L 5901:127.0.0.1:5900 \
  -i "$HOME/.ssh/osxhost_build" \
  -p "$MAC_PORT" "$MAC_USER@$MAC_HOST"

2단계 · 로컬 Mac → 클라우드 Mac

기존 환경 전체를 복사하지 말고 세 경로로 나눠 이전하세요

데이터와 툴체인을 확인한 뒤 CI 실행기를 등록하세요. ‘로그인 가능’과 ‘무인 빌드 가능’을 따로 점검해 로컬 대화형 설정을 파이프라인 설정으로 착각하지 않도록 하세요.

경로 01 · 데이터

프로젝트만 옮기고 비밀 정보는 평문으로 옮기지 않기

  • 키체인에서 필요한 서명 인증서와 개인 키를 암호화된 .p12파일로 내보내고, 암호는 별도의 안전한 채널로 전달하세요.
  • 현재 프로젝트에 필요한 프로비저닝 프로파일만 이전하고 팀, 앱 식별자, 유효 기간을 확인하세요.
  • 로그인 키체인 전체를 복사하지 마세요. 빌드 전용 키체인을 만들고 필요한 항목만 가져온 뒤 서명 도구에 권한을 부여하세요.
  • 소스 코드와 필요한 리소스를 동기화하고 의존성 디렉터리, DerivedData, 이전 빌드 산출물은 제외하세요.

완료 조건: 프로젝트 버전이 일치하고, 인증서에 개인 키가 포함되어 있으며, 민감한 파일이 Git에 들어가지 않음.

경로 02 · 툴체인

버전을 고정한 뒤 의존성 설치

  • 다음 명령으로 xcodes list 버전을 확인하고 필요한 라이선스에 동의한 뒤 대상 Xcode를 설치하세요.
  • 다음 명령으로 xcode-select 현재 툴체인을 명시적으로 선택하세요. GUI에서 마지막으로 선택한 항목에 의존하지 마세요.
  • Homebrew와 arm64 툴체인을 확인하고 Intel 환경의 의존성 디렉터리를 그대로 가져오지 마세요.
  • Ruby 버전 관리자와 Gemfile을 사용해 Ruby, CocoaPods, fastlane 버전을 고정하세요.

완료 조건: 터미널과 CI의 Xcode, Ruby, Bundler 버전이 일치함.

경로 03 · CI 연동

신뢰할 수 있는 작업만 실행기에 전달

  • GitHub Actions: 저장소 설정에서 self-hosted runner를 등록하고 macOS ARM64 패키지를 선택하세요.
  • GitLab Runner: 프로젝트 수준 실행기를 등록하고 필요에 따라 shell 실행 방식을 선택한 뒤 프로젝트 범위를 제한하세요.
  • Jenkins: macOS agent를 만들고 격리된 작업 디렉터리와 통제된 자격 증명 주입 방식을 사용하세요.
  • 먼저 동시 실행 하나로 서명 빌드를 완성한 뒤, 메모리 사용량이 가장 높을 때를 확인하고 동시 실행 수를 늘리세요.

완료 조건: 신뢰할 수 있는 커밋을 한 번 푸시해 로그와 빌드 산출물을 회수할 수 있음.

로컬에서 프로젝트 동기화 · 원격 파일 유지
rsync -avz \
  --exclude='.git/' \
  --exclude='build/' \
  --exclude='DerivedData/' \
  --exclude='Pods/' \
  --exclude='node_modules/' \
  -e "ssh -p $MAC_PORT -i $HOME/.ssh/osxhost_build" \
  ./ "$MAC_USER@$MAC_HOST:~/build-workspace/"

이 동기화 명령은 원격 파일을 삭제하지 않습니다. 정식 파이프라인에서는 버전 관리 저장소에서 고정된 커밋을 체크아웃하세요. 캐시 키에는 최소한 툴체인 버전, 의존성 잠금 파일, 대상 아키텍처가 포함되어야 합니다. 신뢰할 수 없는 외부 병합 요청이 서명 자격 증명에 접근하지 못하게 하세요.

3단계 · 서명과 산출물을 따로 확인

Fastfile 하나로 match, gym, pilot 연결

먼저 프로젝트 루트에서 bundle install을 실행하고 Gemfile.lock을 커밋하세요. 아래 절차는 서명 저장소가 이미 초기화되었고 앱이 생성되어 있으며 CI에 환경 변수가 안전하게 주입된 상태를 전제로 합니다. 새로운 서명 자산을 만드는 절차는 포함하지 않습니다.

원격 사전 점검 · 먼저 툴체인 확인
xcode-select -p
xcodebuild -version
xcodebuild -showsdks
ruby --version
bundle --version
bundle exec fastlane --version
security find-identity -v -p codesigning
df -h "$HOME"

서명 저장소 주소는 MATCH_GIT_URL에, 복호화 암호는 MATCH_PASSWORD에 설정하세요. 업로드 인증에는 서버 측 API 키를 사용합니다. 키 파일 경로는 ASC_KEY_PATH 로 지정하고, 키 ID와 발급자 ID는 각각 ASC_KEY_ID, ASC_ISSUER_ID 로 지정하세요. 키 파일은 CI 비밀 저장소에서 관리하고 실행 중에만 파일로 생성하세요. 프로젝트 저장소에 커밋하지 마세요.

다음을 설정하세요: IOS_APP_IDENTIFIER, IOS_TEAM_ID, IOS_WORKSPACE 및 IOS_SCHEME. 서명 저장소의 배포 유형은 appstore 와 일치해야 합니다. 먼저 공유 scheme을 확인한 다음 전체 파이프라인을 실행하세요.

fastlane/Fastfile · 단일 앱용 TestFlight 파이프라인
default_platform(:ios)

platform :ios do
  desc "Archive a signed build and upload it to TestFlight"
  lane :release do
    setup_ci

    api_key = app_store_connect_api_key(
      key_id: ENV.fetch("ASC_KEY_ID"),
      issuer_id: ENV.fetch("ASC_ISSUER_ID"),
      key_filepath: ENV.fetch("ASC_KEY_PATH"),
      in_house: false
    )

    match(
      type: "appstore",
      readonly: true,
      git_url: ENV.fetch("MATCH_GIT_URL"),
      app_identifier: ENV.fetch("IOS_APP_IDENTIFIER"),
      team_id: ENV.fetch("IOS_TEAM_ID"),
      api_key: api_key
    )

    gym(
      workspace: ENV.fetch("IOS_WORKSPACE"),
      scheme: ENV.fetch("IOS_SCHEME"),
      configuration: "Release",
      clean: true,
      export_method: "app-store",
      output_directory: "build/artifacts",
      output_name: "release.ipa"
    )

    pilot(
      api_key: api_key,
      ipa: "build/artifacts/release.ipa",
      skip_waiting_for_build_processing: true
    )
  end
end
실행 및 확인 · CI에서 환경 변수 주입
bundle exec fastlane ios release
find build/artifacts -maxdepth 1 -type f -print

업로드가 성공해도 처리가 완료된 것은 아닙니다.이 예시는 업로드 후 처리 완료를 기다리지 않습니다. 배포 콘솔에서 빌드 처리 결과를 확인하고, 업로드할 때마다 고유한 빌드 번호를 사용하세요.readonly: true 로 설정하면 CI가 서명 저장소를 수정하지 못합니다. 인증서나 프로비저닝 프로파일이 만료되면 통제된 환경에서 갱신한 뒤 다시 실행하세요.

크로스 플랫폼 프로젝트 · 잠금 파일 유지

iOS 단계를 별도로 실행하고 캐시를 갱신 가능하게 관리

Flutter와 React Native의 iOS 단계는 모두 Xcode, CocoaPods, 서명 환경에 의존합니다. 먼저 플랫폼 진단을 통과한 뒤 업로드 단계를 연동하세요. 캐시가 적중하더라도 의존성 일치 여부를 확인해야 합니다.

Flutter: 시뮬레이터에서 확인한 뒤 IPA 내보내기

Flutter SDK 버전을 고정하고 pubspec.lock을 유지하세요. Pub 다운로드 캐시를 재사용하고, Pods 캐시 키에는 Podfile.lock을, DerivedData 캐시 키에는 Xcode 버전과 프로젝트 설정을 사용하세요. 캐시가 비어 있는 첫 실행은 이후 실행보다 다운로드와 컴파일에 시간이 더 걸립니다.

프로젝트 루트에서 아래 명령을 실행하세요. 서명 설정을 통과해야flutter build ipa --release 로 실기기용 빌드를 내보낼 수 있습니다.

서로 다른 Xcode 메이저 버전 간에 빌드 캐시를 재사용하지 마세요

React Native: Node, Ruby, Pods 버전 고정

JavaScript 패키지 관리자의 잠금 파일을 커밋하고 프로젝트 규칙에 따라 설치 명령을 사용하세요. 아래는 npm 예시입니다. Ruby 의존성은 Bundler로 관리해 시스템 gem과 프로젝트 버전이 서로 덮어쓰지 않도록 하세요.

새 의존성을 추가해 인덱스를 갱신해야 하는 경우에만 pod install --repo-update를 사용하세요. 평소에는 bundle exec pod install을 실행하고 매번 Pods와 잠금 파일을 삭제하지 마세요.

아키텍처와 의존성 잠금 파일을 캐시 키에 포함
Flutter 프로젝트 루트
flutter --version
flutter doctor -v
flutter pub get
flutter build ios --simulator
flutter build ipa --release
React Native 프로젝트 루트 · npm 사용 프로젝트
node --version
npm ci
bundle install
(cd ios && bundle exec pod install)
xcrun simctl list devices available

시뮬레이터 병렬 테스트 수를 제한하세요.사용 가능한 기기 목록에서 실제 UDID 두 개를 가져와 다음 변수에 설정하세요: SIM_UDID_A, SIM_UDID_B. 그런 다음 IOS_WORKSPACE및IOS_SCHEME 이 설정된 프로젝트에서 아래 테스트를 실행하세요. 처음에는 대상 하나로 시작하고, 이후 두 개로 늘리면서 메모리 사용량과 디스크 쓰기를 살펴보세요.

Xcode 테스트 · 프로젝트에서 지원하는 시뮬레이터 대상 두 개 사용
xcodebuild test \
  -workspace "$IOS_WORKSPACE" \
  -scheme "$IOS_SCHEME" \
  -destination "platform=iOS Simulator,id=$SIM_UDID_A" \
  -destination "platform=iOS Simulator,id=$SIM_UDID_B" \
  -parallel-testing-enabled YES \
  -maximum-concurrent-test-simulator-destinations 2 \
  -resultBundlePath "build/TestResults-$(date +%s).xcresult"

컨테이너 범위 · Linux 서비스와 macOS 빌드 분리

컨테이너는 보조 서비스를 실행할 수 있지만 Xcode 호스트를 대체할 수 없습니다

각 노드는 전용 물리 Mac입니다. Docker Desktop이나 OrbStack의 Linux 컨테이너 환경은 Linux 실행 환경에서 동작합니다. ‘물리 노드’라고 해서 컨테이너와 macOS가 같은 커널을 공유한다고 오해하지 마세요.

작업 권장 실행 환경 주의 사항
테스트 데이터베이스, 캐시, 내부 API Linux 컨테이너 가능하면 arm64 이미지를 선택하고 메모리 사용량과 수신 대상을 제한하세요.
Xcode archive 및 시뮬레이터 테스트 macOS 호스트 macOS SDK, 서명 도구, 시뮬레이터 런타임이 필요합니다.
amd64 이미지만 제공되는 서비스 먼저 호환성을 확인하세요 에뮬레이션으로 오버헤드가 늘 수 있으므로 arm64 성능을 그대로 기대할 수 없습니다.
장기 실행 보조 서비스 리소스 격리 요구 사항에 따라 결정 빌드와 물리 메모리, SSD, 네트워크를 공유하므로 리소스를 제한하고 백업을 유지해야 합니다.
컨테이너 도구를 설치하고 실행한 뒤 점검
uname -m
docker version
docker info --format '{{.Architecture}}'
docker system df
docker stats --no-stream

Docker Desktop 또는 OrbStack 중 하나를 선택해 컨테이너 환경을 관리하세요. 현재 Docker context가 어디를 가리키는지 불분명해지는 일을 방지할 수 있습니다. 서비스를 추가하기 전에 여유 메모리와 디스크 공간을 기록하고, 컨테이너 데이터 삭제를 기본 문제 해결 방법으로 삼지 마세요.

확장 기능 · 기기를 확인한 뒤 쓰기

캐시는 확장 디스크로 옮기고 여러 대를 한 대처럼 취급하지 마세요

외장 SSD와 Thunderbolt 5 병렬 연결은 별도 구성 항목입니다. 실제 기기 이름, 마운트 경로, 인터페이스 지원 여부, 연결 토폴로지는 주문 제공 정보를 기준으로 확인하세요. 확인이 끝나기 전에는 포맷이나 파티션 명령을 실행하지 마세요.

외장 SSD: 마운트 지점과 권한 확인

다음 명령으로 diskutil list external 외장 기기를 식별한 다음 개별 볼륨 정보를 확인하세요. 볼륨이 포맷되어 있지만 마운트되지 않았다면 식별자를 확인한 뒤 diskutil mount "$DISK_IDENTIFIER"를 실행하세요. 디스크 번호만 보고 외장 SSD라고 단정하지 마세요.

의존성 캐시, DerivedData, 임시 산출물을 저장하는 데 적합합니다. 실제 경로를 CI 변수 BUILD_CACHE_ROOT에 설정하고 빌드 사용자에게 쓰기 권한이 있는지, 재시작 후에도 경로가 유효한지 확인하세요. 소스 코드와 서명 자료는 별도로 백업해 두세요.

용량 확장은 백업이 아닙니다

TB5 병렬 연결: 기기별 연결 토폴로지 확인

병렬 연결만으로 여러 기기의 CPU나 통합 메모리가 하나의 프로세스가 사용할 리소스 풀로 합쳐지지는 않습니다. 애플리케이션이나 CI 스케줄러에서 작업을 나눠야 하며, 각 기기에 툴체인, 실행기, 작업 디렉터리를 따로 구성해야 합니다.

제공 구성에 Thunderbolt 네트워크 연결이 포함된 경우 기기별 네트워크 인터페이스, 주소, 라우팅을 확인한 다음 직접 연결을 점검하세요. 구매 전에 티켓으로 기기 수, 모델, 전송 용도를 알려 주세요. 모든 모델의 인터페이스 기능이 같다고 가정하지 마세요.

분산 작업은 애플리케이션 자체에서 지원해야 합니다
원격 읽기 전용 점검 · 디스크와 네트워크를 변경하지 않음
diskutil list external
ls -la /Volumes
df -h
system_profiler SPThunderboltDataType
networksetup -listallhardwareports

확장 옵션의 4가지 대여 기간별 가격은 요금제 페이지에서 확인하세요. 기존 주문을 변경하려면 먼저 티켓을 제출해 작업 방식과 데이터 이전 계획을 확인하세요. 운영 빌드 중에 디스크 구성을 직접 변경하지 마세요.

마지막 단계 · 증거를 바탕으로 문제 찾기

최소한의 진단 정보를 수집한 뒤 설정을 변경하세요

주문 번호, 노드, 발생 시각과 시간대, 마지막으로 성공한 빌드의 커밋 번호를 기록하세요. 첫 번째 오류와 종료 코드를 보관하고 로그의 마지막 줄만 보내지 마세요. 아래 명령은 원인을 좁히는 데 도움을 주며 모든 문제를 자동으로 해결하지는 않습니다.

01

SSH 연결 실패

주소, 포트, 사용자 이름, 개인 키 경로부터 확인하세요. 연결 시간 초과라면 먼저 네트워크 연결을 점검하고, 인증 실패라면 사용자 이름, 공개 키 승인, 권한을 확인하세요. 호스트 지문이 바뀌었다면 무시하지 말고 먼저 티켓으로 확인하세요.

로컬 터미널 · 출력에 호스트 정보가 포함될 수 있음
ssh -vv -o ConnectTimeout=10 \
  -i "$HOME/.ssh/osxhost_build" \
  -p "$MAC_PORT" "$MAC_USER@$MAC_HOST"
연결 문제 티켓 제출
02

인증서 만료 또는 서명 ID를 찾을 수 없음

인증서에 개인 키가 포함되어 있는지, 만료되지 않았는지, 프로비저닝 프로파일이 앱 및 팀과 일치하는지 확인하세요. CI 전용 키체인도 빌드 세션에서 접근 가능해야 합니다. 통제된 환경에서 서명 자산을 먼저 수정한 뒤 읽기 전용 match로 동기화하세요.

원격 터미널 · 서명 ID 및 키체인 목록 확인
security find-identity -v -p codesigning
security list-keychains -d user
bundle exec fastlane match appstore --readonly
서명 문제 티켓 제출
03

디스크 부족으로 archive 저장 실패

먼저 시스템 볼륨과 외장 볼륨을 구분하고 DerivedData, 시뮬레이터, 빌드 산출물의 사용량을 확인하세요. 다시 생성할 수 있음을 확인한 캐시만 정리하세요. 키체인, 서명 파일, 사용 중인 작업 디렉터리를 일괄 삭제하지 마세요.

원격 터미널 · 사용량만 확인
df -h
du -sh "$HOME/Library/Developer/Xcode/DerivedData" \
  "$HOME/Library/Developer/CoreSimulator" \
  "$HOME/build-workspace" 2>/dev/null
xcrun simctl list devices unavailable
디스크 문제 티켓 제출
04

Xcode 인덱싱이 오래 멈춰 있음

먼저 인덱싱 프로세스가 여전히 작업 중인지 확인한 다음 의존성 설치와 남은 디스크 공간을 점검하세요. GUI 인덱싱 완료와 명령줄 빌드 완료는 같은 기준이 아닙니다. 로그를 확인해 archive가 실제로 차단되었는지 판단할 수 있습니다.

원격 터미널 · 프로세스 및 빌드 설정 확인
ps -axo pid,%cpu,%mem,etime,comm | grep -E 'Xcode|SourceKit|swift'
xcode-select -p
xcodebuild -list -workspace "$IOS_WORKSPACE"
df -h "$HOME"
Xcode 문제 티켓 제출

티켓에 개인 키, 인증서 암호, 액세스 토큰, 서명 저장소 전체 내용을 첨부하지 마세요.제출하기 전에 로그의 자격 증명과 민감한 경로를 가리세요. 콘솔에 로그인할 수 없다면 다음 주소로 이메일을 보내세요: support@osxhost.com주문 번호와 문제 상황을 알려 주세요. 이메일과 콘솔 티켓은 공개 지원 채널입니다.

먼저 archive 하나를 성공시킨 뒤 동시 실행을 늘리세요

툴체인 버전, 서명 자격 증명 주입, 캐시 키, 산출물 디렉터리를 파이프라인에 명시하세요. Mac이 필요하면 주문하고, 단계에서 막히면 진단 결과와 함께 티켓을 제출하세요.