[iOS] Xcode Cloud를 떠나 Jenkins + Fastlane으로 로컬 CI/CD 구축하기 (feat. Tuist, nGrok)

JH00N·2026년 1월 15일

iOS

목록 보기
1/2
post-thumbnail

평소 Xcode Cloud를 사용했지만 main, release, develop (Release Note) 등 번거로운 배포 과정과 빌드 기다리는 시간이 너무 아까웠다

목차

  1. 전체 아키텍처
  2. 사전 준비
  3. Jenkins 설정
  4. Fastlane 설정
  5. GitHub Webhook 설정
  6. 트러블슈팅

전체 아키텍처

워크플로우

  1. GitHub Repository: Push 또는 PR 이벤트 발생
  2. ngrok Tunnel: GitHub Webhook을 로컬 Jenkins 서버로 전달
  3. Jenkins (Local Mac):
    • JOBIS_iOS_develop: PR 검증 (빌드 테스트)
    • JOBIS_iOS_release: App Store 배포 (Main Merge 시)
  4. Fastlane: 버전 펌핑, 빌드, 사이닝, App Store Connect 업로드 수행

프로젝트 구조 및 파일 관리

Jenkins가 빌드 시 보안 파일(Firebase 설정, XCConfig)을 주입하는 방식을 사용함.

JOBIS-DSM-iOS-v2/
├── Projects/
│   └── App/
│       └── Resources/
│           └── Firebase/           # Jenkins가 원본 경로에서 복사하여 주입
│               ├── GoogleService-Info-DEV.plist
│               └── ...
├── XCConfig/                       # Jenkins가 원본 경로에서 복사하여 주입
│   └── JOBIS-DSM-iOS-v2/
│       ├── Shared.xcconfig
│       └── ...
├── fastlane/
│   ├── Fastfile                    # 배포 스크립트
│   ├── .env                        # 환경변수 (비밀번호 등)
│   └── AuthKey_9WAHTJ84G4.p8       # App Store Connect API Key
└── Tuist/                          # 프로젝트 생성 도구

사전 준비

1. 개발 환경 세팅

기본적인 빌드 및 자동화 도구 설치를 진행함.

# Homebrew & mise 설치
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install mise
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc

# Jenkins & ngrok 설치
brew install jenkins-lts
brew services start jenkins-lts
brew install ngrok
ngrok config add-authtoken [YOUR_AUTHTOKEN]

# Ruby 환경 구성 (OpenSSL 1.1 필수 - 호환성 이슈 방지)
brew install openssl@1.1
RUBY_CONFIGURE_OPTS="--with-openssl-dir=/opt/homebrew/opt/openssl@1.1" \
  mise install ruby@3.2.6
mise use -g ruby@3.2.6

2. 프로젝트 의존성 설치

cd ~/Desktop/프로젝트 경로

# Tuist 설치
mise install tuist@4.119.2

# Fastlane 의존성 설치
git submodule update --init --recursive
cd fastlane
mise exec ruby@3.2.6 -- gem install bundler -v 2.5.22
mise exec ruby@3.2.6 -- bundle install

3. 필수 보안 파일 준비

FCM을 사용하는 프로젝트는 googleInfo 파일이 필수이고 XCConfig 같은 보안 파일은 따로 설정해준다.
Jenkins가 빌드 시 사용할 원본 파일들을 로컬 특정 경로에 미리 배치함.

  • Firebase 설정 파일: ~/Desktop/JOBIS-GoogleInfo/Firebase/ 경로에 위치시킴.
  • XCConfig 파일: ~/Desktop/JOBIS-v2-XCConfig/XCConfig/ 경로에 위치시킴.
  • App Store Connect API Key: Apple Developer Portal에서 생성 후 fastlane/AuthKey_....p8 위치에 저장.

Jenkins 설정

1. 기본 설정

  • 접속: http://localhost:8080
  • 플러그인 설치: Git, GitHub, Pipeline 플러그인 필수 설치.
  • GitHub 인증: Personal Access Token(repo, admin:repo_hook 권한) 생성 후 Jenkins Credentials(Secret text)에 등록.

2. Job 구성 및 실행 전략

  • JOBIS_iOS_develop (PR 및 개발 검증)
    • 실행 조건: main 브랜치가 아닌 develop 브랜치에서 변경사항이 발생하거나 PR(Pull Request)이 생성될 때 실행됨.
    • 주요 역할: 프로젝트 생성 및 단순 빌드 성공 여부를 체크하여 코드 안정성을 검증함.
  • JOBIS_iOS_release (상용 배포)
    실행 조건: main 브랜치에 코드가 머지되거나 직접 푸시될 때 작동함.
    주요 역할: develop Job과 달리 Fastlane이 자동 실행되어 App Store Connect에 빌드를 업로드함.
    * 자동화 특징: 배포 시마다 Info.plist 내의 앱 버전 및 빌드 번호를 자동으로 카운팅한다

    pr에서 진행상태를 확인가능

Fastlane 설정

1. 환경 변수 (.env)

민감한 정보는 .env 파일로 분리하여 관리함.

# Apple Account
APPLE_ID=leejjh080628@gmail.com
TEAM_ID=#######

# Fastlane Auth
FASTLANE_USER=leejjh080628@gmail.com
FASTLANE_PASSWORD=####-####-####-####

# App Store Connect API Key
APP_STORE_CONNECT_API_KEY_KEY_ID=######
APP_STORE_CONNECT_API_KEY_ISSUER_ID=######
APP_STORE_CONNECT_API_KEY_KEY=./fastlane/AuthKey_######.p8

2. Fastfile 핵심 로직

버전 관리와 빌드, 업로드를 자동화함.

default_platform(:ios)

platform :ios do
  desc "PROD 앱 배포"
  lane :release do
    # 1. 버전 펌핑 (예: 2.3.9 -> 3.0.0)
    # ... (버전 파싱 및 Plist 수정 로직)

    # 2. Tuist 프로젝트 생성
    Dir.chdir(PROJECT_ROOT) do
      sh("mise", "exec", "--", "tuist", "generate")
    end

    # 3. 빌드 및 IPA 추출
    build_app(
      scheme: "JOBIS-DSM-iOS-v2-PROD",
      export_method: "app-store",
      # ...
    )

    # 4. App Store 업로드
    upload_to_app_store(
      skip_screenshots: true,
      submit_for_review: false,
      # ...
    )
  end
end

GitHub Webhook 설정

로컬 Jenkins를 외부(GitHub)에서 접근할 수 있도록 ngrok을 사용함.

  1. ngrok 실행:
    nohup ngrok http 8080 --log stdout > /tmp/ngrok.log 2>&1 &
  2. Webhook 등록:
    • GitHub Repo Settings > Webhooks > Add webhook
    • Payload URL: https://[ngrok-url]/github-webhook/
    • Events: Push, Pull request

최종 워크플로우

  • feature등 각 브랜치 개발 -> develop 머지 (pr 진행 상황 확인) Ci 자동 실행
  • develop -> main 머지, Release Job 실행 , 실행 성공 시 빌드 번호 상승, fastlane을 통한 배포

트러블슈팅

1. ngrok Webhook 404 에러

  • 증상: GitHub webhook failed: 404 Not Found
  • 원인: ngrok 세션 만료 또는 인증 토큰 누락.
  • 해결: 프로세스 종료 후 authtoken 재등록 및 재실행.

2. GoogleService-Info.plist 누락

  • 증상: PhaseScriptExecution GoogleService-Info.plist failed
  • 원인: .gitignore 처리된 파일이 Jenkins Workspace에 없음.
  • 해결: Jenkins Shell Script 단계에서 로컬 원본 경로로부터 cp 명령어로 복사하도록 수정함.

3. ProjectDescriptionHelpers 모듈 에러

  • 증상: no such module 'ProjectDescriptionHelpers'
  • 원인: Tuist 버전 호환성 문제 (v4.x vs v3.x 설정 파일 충돌).
  • 해결: 구 버전 설정 파일(Tuist.swift) 삭제 및 캐시 초기화(rm -rf ~/.cache/tuist).

4. Code Signing 에러

  • 증상: No signing certificate "iOS Distribution" found
  • 원인: Jenkins가 실행되는 Mac의 키체인에 배포용 인증서가 없거나 Xcode 계정 연동 미비.
  • 해결: Xcode Settings > Accounts에서 Apple ID 로그인 및 인증서(Distribution) 로컬 생성 확인.

5. OpenSSL 관련 Ruby 에러

  • 증상: OpenSSL::PKey::ECError: invalid curve name
  • 원인: 최신 Ruby와 OpenSSL 3.x 간의 호환성 문제.
  • 해결: OpenSSL 1.1을 설치하고, Ruby 설치 시 해당 OpenSSL 경로를 참조하도록 강제하여 재설치함.

6. invalid byte sequence (xcpretty)

  • 증상: Jenkins 빌드 로그 파싱 중 인코딩 에러 발생.
  • 해결: 스크립트 상단에 export LC_ALL=en_US.UTF-8 추가.

단점 / 개선할 점

  • 기존 프로젝트 Tuist 버전이 3.x.x 였기때문에 호환성 문제가 있어 4.xx대로 마이그레이션 진행, 마이그레이션 시 에러 발생

  • no such module 에러

  • ngrok의 한계와 대안
    현재 가장 큰 걸림돌은 ngrok의 연결 불안정성
    학교 와이파이 환경이나 맥북이 절전 모드에 들어가면 터널링이 끊김
    세션 만료로 인해 URL이 바뀌면 Webhook 설정을 매번 변경해야 하는 번거로움이 존재
    협업 시 다른 팀원이 PR을 날렸을 때 내 로컬 서버가 꺼져있으면 CI가 돌지 않는 치명적인 문제

  • 해결 방안: Cloudflare Tunnel
    이를 해결하기 위해 다음 스텝으로 Cloudflare Tunnel 도입을 고려하고 있습니다. ngrok보다 연결이 안정적이고 도메인 고정이 용이하여 로컬 CI/CD 환경의 단점을 보완해 줄 것으로 기대합니다.


환경 정보

  • macOS: Darwin 25.2.0
  • Xcode: 16.2
  • Jenkins: 2.528.3
  • Ruby: 3.2.6 (OpenSSL 1.1)
  • Tuist: 4.119.2
  • Fastlane: 2.230.0

0개의 댓글