본문 바로가기
CI/CD

인증서를 포함한 GitHub actions 기반의 자동 Capacitor iOS build

fastlane과 GitHub Actions를 사용하여 5분 만에 iOS Ionic 앱 CI/CD 파이프라인 구축하기 (2024)

인증서를 포함한 GitHub actions 기반의 자동 Capacitor iOS build

Capacitor 앱의 CI/CD 설정은 복잡하고 시간이 많이 걸릴 수 있습니다. 다음 내용을 확인해 보세요:

이제 다음을 권장합니다: Capgo CLI를 활용한 Capgo Build 네이티브 Capacitor 빌드를 위해. 이 Fastlane 가이드는 기존 GitHub Actions 파이프라인을 유지 관리하는 팀을 위한 것입니다. 하지만 새로운 iOS 빌드는 Capgo CLI를 사용하는 것이 좋습니다. Fastlane, Xcode runner, 인증서 및 업로드 스크립트를 직접 관리할 필요가 없기 때문입니다.

Capgo이 제공하는 CI/CD용 Capgo Build

Fastlane, Xcode runner, 인증서, provisioning profile 및 업로드 스크립트 관리의 번거로움을 없애세요. Capgo 빌드 기존 CI/CD 파이프라인에서 서명된 네이티브 iOS 빌드를 실행합니다:

  • 기존 파이프라인과 연동 가능: 웹 빌드 완료 후 GitHub Actions, GitLab CI, Jenkins 또는 로컬 스크립트에서 Capgo Build를 트리거하세요. npx cap sync.
  • CI secrets를 통한 서명: App Store Connect 키, 인증서, provisioning profile, 비밀번호 및 team ID를 자체 CI secrets에 안전하게 보관하세요.
  • 네이티브 runner 관리 불필요: Capgo Build는 관리형 Apple 빌드 환경을 제공하므로 macOS runner, Xcode 이미지 또는 Fastlane lane을 직접 관리할 필요가 없습니다.
  • 아티팩트 및 제출: QA를 위한 서명된 아티팩트를 다운로드하거나 CLI Capgo를 통해 릴리스 빌드를 제출하세요.

요금제

  • Capgo 플랜은 월 $12부터 시작합니다
  • OTA 업데이트 및 월 약 15회의 Native Build 포함
  • 추가 빌드 시간은 크레딧을 통해 분 단위로 과금됩니다

CI/CD에 Capgo Build 설정하기

수동 설정 가이드

다음 단계를 진행하세요:

Fastlane과 GitHub Actions 및 인증서를 사용한 iOS 지속적 배포(CD)

사전 요구사항

튜토리얼을 시작하기 전에:

  • Fastlane이 설치되어 있는지 개발 환경에서 확인하세요.
  • iOS developer program 멤버십에 가입되어 있는지 확인하세요.

가격 관련 중요 정보

GitHub Action 가격

https://github.com/features/actions

해당 서비스는 ‘무료 선택한 머신에 따라 제한 범위 내에서 이용 가능합니다.
다음 환경을 사용합니다: macOS 스크린샷에서 확인하실 수 있듯이 가격과 제한 사항이 있습니다 (가격은 튜토리얼 작성 시점 기준이며, 향후 변경될 수 있습니다).

요구 사항과 가격을 확인했으니, 계속 진행해 보겠습니다.

참고: 이 포스트에서는 App Store Connect에 앱이 이미 생성되어 있다고 가정합니다. 중요한 정보는 Fastlane이 자동으로 복사합니다!

이 튜토리얼에서 배우는 내용

포스트 진행 단계

  1. Fastlane과 함께 App Store Connect API 사용하기
    • 요구 사항:
      • App Store Connect API 키 생성하기
      • App Store Connect API Key 사용하기
  2. Fastlane 파일 복사
  3. GitHub Actions 설정하기

1. Fastlane과 함께 App Store Connect API 사용하기

2021년 2월부터 App Store Connect에 로그인하는 모든 사용자는 2단계 인증을 반드시 사용해야 합니다. Apple ID에 추가된 이 보안 계층은 귀하의 계정에 접근할 수 있는 유일한 사람이 본인임을 보장합니다.
출처: Apple 지원

요구 사항

Fastlane이 앱 업로드를 위해 App Store Connect API를 사용하려면 다음 세 가지 항목이 필요합니다:

  1. Issuer ID
  2. Key ID
  3. Key 파일 또는 Key 내용

App Store Connect API Key 발급받기

키를 생성하려면 App Store Connect의 Admin 권한이 있어야 합니다. 권한이 없는 경우, 관련 담당자에게 이 문서를 전달해 주세요.

  1. 로그인: App Store Connect.

  2. 선택 사용자 및 액세스.

App Store Connect 사용자 액세스

3 — Integration 탭을 선택합니다.

App Store Connect API 연동

  1. API Key 생성 또는 추가(+) 버튼을 클릭합니다.

App Store Connect API 키 생성

  1. 키 이름을 입력하세요. 이 이름은 참조용이며 키 자체의 일부는 아닙니다.

App Store Connect API 키 생성 이름

6 — Access 항목에서 키의 역할을 선택합니다. 키에 적용되는 역할은 팀 사용자에게 적용되는 역할과 동일합니다. 자세히 보기 역할 권한. 다음을 선택하는 것을 권장합니다 앱 관리자.

  1. Generate를 클릭하세요.

API 키의 액세스 권한은 특정 앱으로 제한할 수 없습니다.

새 키의 이름, 키 ID, 다운로드 링크 및 기타 정보가 페이지에 표시됩니다.

App Store Connect 다운로드 키

필요한 세 가지 정보를 여기에서 모두 확인할 수 있습니다.
<1> 이슈 ID. (APPLE_ISSUER_ID 비밀번호)
<2> Key ID. (APPLE_KEY_ID 비밀번호)
<3> “Download API Key”를 클릭해 API private key를 다운로드하세요. 다운로드 링크는 private key를 아직 다운로드하지 않은 경우에만 표시됩니다. Apple은 private key 사본을 보관하지 않으므로 한 번만 다운로드할 수 있습니다.

🔴 private key는 안전한 곳에 보관하세요. 키를 절대 공유하거나, code repository에 저장하거나, 클라이언트 측 code에 포함해서는 안 됩니다.

App Store Connect API Key 사용하기

인증용 JWT 토큰을 생성하려면 API 키 파일(다운로드한 p8 파일), 키 ID, 그리고 Issuer ID가 필요합니다. 이 정보를 Fastlane에 전달하는 방법은 여러 가지가 있지만, 여기서는 Fastlane의 새로운 action을 사용하겠습니다. app_store_connect_api_key. 다른 방법은 다음에서 확인할 수 있습니다. Fastlane 문서. 환경 변수를 설정할 수 있는 대부분의 CI 환경에서 가장 간편한 방식이라고 생각해 이 방법을 소개합니다.

다운로드한 p8 파일을 Base64로 변환한 뒤 secret으로 저장해 주세요 ("APPLE_KEY_CONTENT).

base64 -i APPLE_KEY_CONTENT.p8 | pbcopy

이제 API 키를 사용하여 Fastlane으로 App Store Connect를 관리할 수 있습니다. 아주 좋네요!

2. 인증서

Xcode를 열고 다음으로 이동하세요 설정 &gt; 계정 &gt; Apple ID &gt; 팀 에서 팀을 선택하세요.

Code 서명 ID

클릭하세요 인증서 관리.

아직 인증서를 생성하지 않았다면, 새로 생성할 수 있습니다.

클릭하세요 + 를 선택한 후 Apple Distribution

Apple Distribution

그 다음 인증서를 다운로드하기 위해 keychain으로 이동하여 .p12 다운로드됩니다.

이를 위해 keychain으로 이동한 뒤 로그인 keychain의 My Certificates 탭을 선택합니다..

My Certificates 탭을 선택합니다.

다운로드할 인증서를 선택하세요. (인증서 날짜를 확인하면 쉽습니다)

인증서의 private key를 우클릭한 후 다음을 선택합니다 내보내기.

파일 형식 선택 개인 정보 교환 (.p12).

그러면 인증서가 파일로 .p12 다운로드됩니다.

터미널에서 파일을 열고 다음 명령어를 사용하여 Base64로 변환하세요:

base64 -i BUILD_CERTIFICATE.p12 | pbcopy

이 값이 여러분의 BUILD_CERTIFICATE_BASE64 secret이 됩니다. 또한, 요청 시 인증서 비밀번호를 입력하세요. 이 비밀번호가 여러분의 P12_PASSWORD secret이 됩니다.

3. 프로비저닝 프로파일

열기 Apple Developer 그리고 올바른 팀을 선택하세요.

그런 다음, 다음을 클릭해 새 프로필을 만드세요. +

새 프로필 만들기

그리고 다음을 선택하세요. App Store Connect.

App Store Connect 선택

그런 다음 올바른 앱을 선택해야 합니다. 와일드카드는 사용할 수 없으며, 사용하면 서명이 실패하니 주의하세요.

올바른 앱 선택

이전에 생성한 올바른 인증서를 선택하세요 (만료일이 오늘과 같은 날짜 및 월인지 확인하세요)한 후 클릭합니다. 계속하기.

올바른 인증서 선택

마지막으로 프로파일 이름을 입력하고 클릭합니다. 생성.

이 이름은 Fastlane에서 다음 값 아래의 프로필을 식별하는 데 사용됩니다: APPLE_PROFILE_NAME.

프로필 생성

프로필을 다음 형식으로 다운로드할 수 있습니다: .mobileprovision 다운로드됩니다.

프로필 다운로드

프로필을 Base64로 변환한 뒤 secret으로 저장하세요 ("BUILD_PROVISION_PROFILE_BASE64).

base64 -i BUILD_PROVISION_PROFILE.mobileprovision | pbcopy

4. Fastlane 파일 복사

Fastlane은 일반적인 모바일 개발 작업을 자동화하기 위해 만들어진 Ruby 라이브러리입니다. Fastlane을 사용하면 보통 Android Studio에서 수행하던 작업을 실행하는 일련의 “actions”를 묶은 커스텀 “lanes”를 구성할 수 있습니다. Fastlane으로 많은 것을 할 수 있지만, 이 튜토리얼에서는 몇 가지 핵심 action만 사용하겠습니다.

Capacitor/Ionic 프로젝트 루트에 Fastlane 폴더를 만들고 그 안에 Fastfile을 추가하세요:

  • 폴더: <project-root>/fastlane/
  • 파일: <project-root>/fastlane/Fastfile

다음과 동일한 레벨입니다: package.json, capacitor.config.*, 그리고 ios/ 폴더입니다. 이 안에 생성하지 마세요. ios/App/.

platform :ios do
  desc 'Export ipa and submit to TestFlight'
  lane :beta do
    keychain_info = { keychain_name: "ios-build-#{Time.now.to_i}.keychain", keychain_password: SecureRandom.uuid }
    
    begin
      setup_signing(keychain_info)
      bump_build_number
      build_app_with_signing(keychain_info)
      submit_to_testflight
    ensure
      cleanup_keychain(keychain_info)
    end
  end

  private_lane :setup_signing do |options|
    create_keychain(
      name: options[:keychain_name],
      password: options[:keychain_password],
      unlock: true,
      timeout: 0,
      lock_when_sleeps: false, 
      add_to_search_list: true
    )
    import_cert(options)
    install_profile
    update_project_settings
  end

  lane :bump_build_number do
		file = File.read('../package.json')
		data_hash = JSON.parse(file)
		api_key = app_store_connect_api_key(
      key_id: ENV['APPLE_KEY_ID'],
      issuer_id: ENV['APPLE_ISSUER_ID'],
      key_content: ENV['APPLE_KEY_CONTENT'],
      is_key_content_base64: true,
      duration: 1200,
      in_house: false
    )
		build_num = app_store_build_number(
      api_key: api_key,
			app_identifier: ENV['BUNDLE_IDENTIFIER'],
			live: false
    )
		build_num = build_num + 1
		UI.message("Bumped build number to #{build_num}")
		increment_build_number(
			build_number: build_num,
			xcodeproj: "./ios/App/App.xcodeproj",
			skip_info_plist: true
		)
	end

  private_lane :import_cert do |options|
    cert_path = "#{Dir.tmpdir}/build_certificate.p12"
    File.write(cert_path, Base64.decode64(ENV['BUILD_CERTIFICATE_BASE64']))
    import_certificate(
      certificate_path: cert_path,
      certificate_password: ENV['P12_PASSWORD'] || "",
      keychain_name: options[:keychain_name],
      keychain_password: options[:keychain_password],
      log_output: true
    )
    File.delete(cert_path)
  end  
  
  private_lane :cleanup_keychain do |options|
    delete_keychain(
      name: options[:keychain_name]
    )
  end  

  private_lane :install_profile do
    profile_path = "#{Dir.tmpdir}/build_pp.mobileprovision"
    File.write(profile_path, Base64.decode64(ENV['BUILD_PROVISION_PROFILE_BASE64']))
    UI.user_error!("Failed to create provisioning profile at #{profile_path}") unless File.exist?(profile_path)
    ENV['PROVISIONING_PROFILE_PATH'] = profile_path
    install_provisioning_profile(path: profile_path)
    File.delete(profile_path)
  end

  private_lane :update_project_settings do
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "./ios/App/App.xcodeproj",
      code_sign_identity: "iPhone Distribution",
      profile_name: ENV['APPLE_PROFILE_NAME'],
      bundle_identifier: ENV['BUNDLE_IDENTIFIER'],
      team_id: ENV['APP_STORE_CONNECT_TEAM_ID']
    )
    update_project_team(
      path: "./ios/App/App.xcodeproj",
      teamid: ENV['APP_STORE_CONNECT_TEAM_ID']
    )
  end

  private_lane :build_app_with_signing do |options|
    unlock_keychain(
      path: options[:keychain_name],
      password: options[:keychain_password],
      set_default: false
    )
    build_app(
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      configuration: "Release",
      export_method: "app-store",
      output_name: "App.ipa",
      export_options: {
        provisioningProfiles: {
          ENV['BUNDLE_IDENTIFIER'] => ENV['APPLE_PROFILE_NAME']
        }
      },
      xcargs: "-verbose",
      buildlog_path: "./build_logs",
      export_xcargs: "-allowProvisioningUpdates",
    )
  end   

  private_lane :submit_to_testflight do
    api_key = app_store_connect_api_key(
      key_id: ENV['APPLE_KEY_ID'],
      issuer_id: ENV['APPLE_ISSUER_ID'],
      key_content: ENV['APPLE_KEY_CONTENT'],
      is_key_content_base64: true,
      duration: 1200,
      in_house: false
    )
    pilot(
      api_key: api_key,
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )
  end
end

5. Secrets 설정하기

GitHub Actions는 다음 단계에서 설정할 repository secrets를 사용합니다. 본인의 로컬 환경에서 Fastlane을 실행하거나 테스트하려는 경우에만 로컬 .env 파일이 필요합니다.

로컬 테스트를 위해 <project-root>/fastlane/.env 옆에 Fastfile를 생성하세요. 이 파일은 커밋하지 마세요. fastlane/.env …에 .gitignore 를 먼저 추가하세요(또는 이미 무시되고 있는지 확인하세요). 예시는 다음과 같습니다:

APP_STORE_CONNECT_TEAM_ID=UVTJ336J2D
BUNDLE_IDENTIFIER=ee.forgr.testfastlane
# See previous section for these secrets
BUILD_CERTIFICATE_BASE64=
BUILD_PROVISION_PROFILE_BASE64=
APPLE_KEY_ID=
APPLE_ISSUER_ID=
APPLE_KEY_CONTENT=
P12_PASSWORD=
APPLE_PROFILE_NAME=

APP_STORE_CONNECT_TEAM_ID 가져오기

다음으로 이동: 개발자 센터 아래로 스크롤하여 Membership details 섹션까지 Team ID 설정해야 할 값입니다. APP_STORE_CONNECT_TEAM_ID secret.

app-store-connect-team-id

BUNDLE_IDENTIFIER 가져오기

  1. Xcode 열기
  2. 다음 항목을 더블 클릭하세요: App 프로젝트 네비게이터의
  3. 그런 다음 탭을 클릭하세요 Signing and Capabilities
  4. 다음 값을 복사하세요: Bundle identifier 값을 복사하세요. 이 값은 BUNDLE_IDENTIFIER secret.
bundle-identifier-xcode

6. 빌드 프로세싱

GitHub Actions에서 비용은 분 단위로 청구됩니다 CI/CD 워크플로우를 실행하는 데 사용했습니다. 제 경험상, App Store Connect에서 빌드가 처리되기까지 약 10~15분 정도 소요됩니다.

비공개 프로젝트의 경우, 빌드당 예상 비용은 최대 $0.08/분 x 15분 = $1.2, 프로젝트 구성 및 종속성에 따라 더 늘어날 수 있습니다.

개인 프로젝트의 비용이 걱정된다면, skip_waiting_for_build_processing 대상 true. 이를 통해 App Store Connect의 빌드 처리 완료를 기다릴 필요가 없어 빌드 시간을 절약할 수 있습니다.

단, 트레이드오프가 있습니다. 빌드를 사용자에게 배포하기 전에 App Store Connect에서 앱의 규정 준수 정보를 수동으로 업데이트해야 합니다.

이 최적화는 빌드 시간에 비용이 발생하는 프라이빗 프로젝트에서 주로 유용합니다. 공개/무료 프로젝트는 빌드 시간이 무료이므로 이 설정을 활성화할 필요가 없습니다. 자세한 내용은 GitHub의 요금 페이지를 참조하세요.

7. GitHub Actions 설정하기

GitHub secrets 구성하기

다음 파일에서 secrets를 복사하여 .env GitHub repository secrets에 붙여넣으세요.

다음으로 이동: 설정 &gt; Secrets 및 변수 &gt; 액션 &gt; 새 레포지토리 Secret

github-secrets

2. BUILD_CERTIFICATE_BASE64 - Base64로 인코딩된 인증서.

3. BUILD_PROVISION_PROFILE_BASE64 - Base64로 인코딩된 프로비저닝 프로파일.

4. BUNDLE_IDENTIFIER - 앱의 bundle identifier.

5. APPLE_KEY_ID — App Store Connect API Key 🔺Key ID.

6. APPLE_ISSUER_ID — App Store Connect API Key 🔺Issuer ID.

7. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 Key 내용: .p8, 확인하세요

8. GitHub workflow 파일 구성

GitHub workflow 디렉토리를 생성합니다.

cd .github/workflows

해당 폴더 안에 workflow 파일을 만들고 이름은 build-upload-ios.yml로 지정한 뒤 다음을 추가하세요.

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: Set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
          bundler-cache: true
      - uses: maierj/fastlane-action@v3.1.0
        env:
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          BUNDLE_IDENTIFIER: ${{ secrets.BUNDLE_IDENTIFIER }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          APPLE_PROFILE_NAME: ${{ secrets.APPLE_PROFILE_NAME }}
        with:
          lane: ios beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v6
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 10

이 workflow는 각 GitHub 태그가 있습니다tag 자동화가 필요하면 다음을 참고하세요: GitHub actions를 이용한 자동 빌드 및 릴리스 먼저.

그다음 이 워크플로우가 NodeJS 의존성을 가져와 설치하고 JavaScript 앱을 빌드합니다.

새 커밋을 보낼 때마다 TestFlight에서 릴리스가 빌드됩니다.

앱은 Ionic을 사용할 필요가 없고 Capacitor 베이스만 필수입니다. 기존 Cordova 모듈이 있어도 되지만 Capacitor JS 플러그인을 사용하는 것이 좋습니다.

8. 워크플로 트리거하기

커밋 생성

커밋을 만들면, 리포지토리에서 활성 워크플로우를 확인할 수 있습니다.

워크플로 트리거하기

브랜치에 새로운 커밋 푸시하기 main 또는 development 워크플로를 트리거하려면.

커밋으로 시작됨

몇 분 후, App Store Connect 대시보드에서 빌드를 확인할 수 있습니다.

Testflight 대시보드

9. 로컬 머신에서 배포할 수 있나요?

네, 아주 간편하게 가능합니다.

언제나 그랬듯 Xcode를 사용하여 앱을 빌드하고 서명할 수 있습니다.

CI/CD 설정 가이드

대체 CI/CD 플랫폼

Live Updates 및 배포

감사합니다

이 블로그는 다음 문서들을 바탕으로 작성되었습니다:

인증서를 활용한 GitHub 액션 기반 자동 Capacitor iOS 빌드 심화 학습

사용 중이라면 인증서를 포함한 GitHub actions 기반의 자동 Capacitor iOS build CI/CD 자동화를 계획하려면, 다음을 연결하세요 Capgo CI/CD 설정하기 Capgo CI/CD의 제품 워크플로우용, Capgo Native Build Capgo Native Builds의 제품 워크플로우용, Capgo 연동 서비스 Capgo Integrations의 제품 워크플로우용, CI/CD 통합 CI/CD Integration의 구현 세부 사항은, 그리고 GitHub Actions 연동 GitHub Actions Integration의 구현 세부 사항을 참고하세요.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.