Match를 활용한 GitHub Actions 기반 iOS 자동 빌드
Capacitor 앱의 CI/CD 설정은 복잡하고 시간이 많이 걸릴 수 있습니다. 다음 내용을 확인해 보세요:
신규 빌드 권장 사항: Capgo Build 사용
이제 다음을 권장합니다: Capgo CLI를 활용한 Capgo Build 네이티브 Capacitor 빌드를 위해. 이 Fastlane Match 가이드는 기존 GitHub Actions 파이프라인을 유지 관리하는 팀을 위한 것입니다. 하지만 새로운 iOS 빌드에는 Capgo CLI를 사용하여 Fastlane, Match 저장소, Xcode runner, 인증서 및 업로드 스크립트를 직접 관리할 필요가 없도록 하세요.
Capgo이 제공하는 CI/CD용 Capgo Build
Fastlane Match, Xcode runner, 인증서, 프로비저닝 프로파일 및 업로드 스크립트 관리의 번거로움을 없애세요. Capgo 빌드 기존 CI/CD 파이프라인에서 서명된 네이티브 iOS 빌드를 실행합니다:
- 기존 파이프라인과 연동 가능: 웹 빌드 완료 후 GitHub Actions, GitLab CI, Jenkins 또는 로컬 스크립트에서 Capgo Build를 트리거하세요.
npx cap sync. - CI secrets를 통한 서명: App Store Connect 키, 인증서, 프로비저닝 프로파일, 비밀번호 및 Team ID를 자체 CI secrets에 안전하게 보관하세요.
- 네이티브 runner 관리 불필요: Capgo Build는 관리형 Apple 빌드 환경을 제공하므로 macOS runner, Xcode 이미지, Fastlane 또는 Match 저장소를 직접 관리할 필요가 없습니다.
- 아티팩트 및 제출: QA를 위한 서명된 아티팩트를 다운로드하거나 Capgo CLI을 통해 릴리스 빌드를 제출하세요.
요금제
- Capgo 플랜은 월 $12부터 시작합니다
- OTA 업데이트 및 월 약 15회의 Native Build 포함
- 추가 빌드 시간은 크레딧을 통해 분 단위로 과금됩니다
수동 설정 가이드
다음 단계를 따라 진행하세요:
match를 사용한 Fastlane 및 GitHub Actions 기반 iOS Continuous Delivery
사전 요구사항
튜토리얼을 계속하기 전에…
- Fastlane이 설치되어 있는지 개발 환경에서 확인하세요.
- iOS 개발자 프로그램 멤버십.
- 읽고 싶은 마음 😆…
- 개발자가 많은 팀이라면, 그렇지 않으면 fastlane cert를 더 간단한 워크플로우에 사용하길 권장합니다.
가격 관련 중요 사항

https://github.com/features/actions
선택한 머신 사양에 따라 제한 범위 내에서 '무료'로 이용 가능합니다.
저희는 macOS 머신을 사용할 예정이며, 스크린샷에서 가격과 제한 사항을 확인할 수 있습니다 (가격은 튜토리얼 작성 시점 기준이며 향후 변경될 수 있습니다).
🔴 요구 사항과 가격을 확인했으니, 괜찮으시면 계속 진행하겠습니다…
📣 이 포스트에서는 iTunes Connect에 앱이 생성되어 있고, Apple 생태계의 인증서를 이미 보유하고 있어 Fastlane이 모든 것을 복사할 수 있다고 가정합니다.
자세히 알아봅시다 🤿
포스트 진행 단계
- Fastlane Match를 사용한 App Store Connect API 설정
- 요구 사항
- App Store Connect API 키 생성하기
- App Store Connect API Key 사용하기
- Fastlane 파일 복사
- Fastlane match 설정하기
1. Fastlane Match로 App Store Connect API 사용하기
2021년 2월부터 App Store Connect에 로그인하려면 모든 사용자가 2단계 인증 또는 이중 인증을 거쳐야 합니다. Apple ID에 추가된 이 보안 계층은 본인만이 계정에 액세스할 수 있도록 보호합니다.
출처: Apple 지원
match를 시작하려면 기존 인증서를 취소해야 합니다. 하지만 걱정 마세요, 즉시 새 인증서를 발급받을 수 있습니다.
요구 사항
App Store Connect API를 사용하려면 Fastlane에 다음 세 가지 항목이 필요합니다.
- Issuer ID
- Key ID
- Key 파일 또는 Key 내용.
App Store Connect API Key 생성하기
Key를 생성하려면 App Store Connect의 Admin 권한이 필요합니다. 권한이 없는 경우, 담당자에게 이 문서를 공유하여 다음 절차를 진행하도록 요청하세요.
1 — 다음 사이트에 로그인하세요: App Store Connect.
2 — 다음을 선택하세요: 사용자 및 액세스.

3 — Integration 탭을 선택하세요.

4 — Generate API Key 또는 추가(+) 버튼을 클릭하세요.

5 — 키 이름을 입력합니다. 이 이름은 식별용이며 키 자체의 일부는 아닙니다.

6 — Access 항목에서 키의 역할을 선택합니다. 키에 적용되는 역할은 팀 사용자에게 적용되는 역할과 동일합니다. 자세히 보기 역할 권한. 다음을 선택하는 것을 권장합니다 앱 관리자.
7 — Generate를 클릭합니다.
API 키의 액세스 권한은 특정 앱으로 제한할 수 없습니다.
새 키의 이름, 키 ID, 다운로드 링크 및 기타 정보가 페이지에 표시됩니다.

필요한 세 가지 정보를 여기에서 모두 확인할 수 있습니다.
<1> Issue ID.
<2> 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 token을 생성하려면 API Key 파일(다운로드한 p8 파일), key ID, issuer ID가 필요합니다. 이 정보들을 Fastlane의 새로운 action을 사용해 Fastlane에 입력하는 방법은 여러 가지가 있습니다 app_store_connect_api_key. 다른 방법은 다음에서 확인할 수 있습니다. Fastlane 문서. 환경 변수를 설정할 수 있는 대부분의 CI 환경에서 가장 간편한 방식이라고 생각해 이 방법을 소개합니다.
이제 App Store Connect API key로 Fastlane을 관리할 수 있습니다. 좋습니다!
2. Fastlane 파일 복사
Fastlane은 일반적인 모바일 개발 작업을 자동화하기 위해 만들어진 Ruby 라이브러리입니다. Fastlane을 사용하면 보통 Android Studio에서 수행하던 작업을 실행하는 일련의 “actions”를 묶은 커스텀 “lanes”를 구성할 수 있습니다. Fastlane으로 많은 것을 할 수 있지만, 이 튜토리얼에서는 몇 가지 핵심 action만 사용하겠습니다.
프로젝트 루트에 Fastlane 폴더를 생성하고 다음 파일들을 복사하세요: Fastfile
default_platform(:ios)
DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
def delete_temp_keychain(name)
delete_keychain(
name: name
) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end
def create_temp_keychain(name, password)
create_keychain(
name: name,
password: password,
unlock: false,
timeout: 0
)
end
def ensure_temp_keychain(name, password)
delete_temp_keychain(name)
create_temp_keychain(name, password)
end
platform :ios do
lane :build do
build_app(
configuration: "Release",
workspace: "./ios/App/App.xcworkspace",
scheme: "App",
export_method: "app-store",
export_options: {
provisioningProfiles: {
DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
}
}
)
end
lane :refresh_profiles do
match(
type: "development",
force: true)
match(
type: "adhoc",
force: true)
end
desc "Register new device"
lane :register_new_device do |options|
device_name = prompt(text: "Enter the device name: ")
device_udid = prompt(text: "Enter the device UDID: ")
device_hash = {}
device_hash[device_name] = device_udid
register_devices(
devices: device_hash
)
refresh_profiles
end
lane :closed_beta do
keychain_name = TEMP_KEYCHAIN_USER
keychain_password = TEMP_KEYCHAIN_PASSWORD
ensure_temp_keychain(keychain_name, keychain_password)
api_key = app_store_connect_api_key(
key_id: APPLE_KEY_ID,
issuer_id: APPLE_ISSUER_ID,
key_content: APPLE_KEY_CONTENT,
duration: 1200,
in_house: false
)
match(
type: 'appstore',
git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
readonly: true,
keychain_name: keychain_name,
keychain_password: keychain_password,
api_key: api_key
)
gym(
configuration: "Release",
workspace: "./ios/App/App.xcworkspace",
scheme: "App",
export_method: "app-store",
export_options: {
provisioningProfiles: {
DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
}
}
)
pilot(
apple_id: "#{DEVELOPER_APP_ID}",
app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
skip_waiting_for_build_processing: true,
skip_submission: true,
distribute_external: false,
notify_external_testers: false,
ipa: "./App.ipa"
)
delete_temp_keychain(keychain_name)
end
lane :submit_review do
version = ''
Dir.chdir("..") do
file = File.read("package.json")
data = JSON.parse(file)
version = data["version"]
end
deliver(
app_version: version,
submit_for_review: true,
automatic_release: true,
force: true, # Skip HTMl report verification
skip_metadata: false,
skip_screenshots: false,
skip_binary_upload: true
)
end
end
Appfile
app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])
Fastlane match 설정하기
Fastlane match 는 iOS의 code signing을 처리하는 새로운 방식입니다. Fastlane match를 사용하면 팀 단위로 iOS 앱에 필요한 인증서와 provisioning profile을 쉽게 관리할 수 있습니다.
다음 이름으로 새로운 private repository를 생성하세요: certificates. 예를 들어 GitHub 개인 계정이나 조직(organization)에 생성할 수 있습니다.
iOS 앱을 위한 Fastlane match를 초기화합니다.
fastlane match init
그 다음 1번 옵션(Git Storage)을 선택하세요.
[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?
새로 생성한 repository의 URL을 입력합니다.
[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>
이제 Fastlane 폴더 안에 다음 파일이 생성되었습니다: Matchfile 및
_git_url_인증서 저장소의 HTTPS URL로 설정해야 합니다. 선택 사항으로 SSH를 사용할 수도 있지만, 별도의 실행 단계가 필요합니다.
# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")
다음으로, Fastlane Match를 통해 인증서를 생성하고 요청 시 자격 증명을 입력합니다.
패스프레이즈를 입력하라는 프롬프트가 나타납니다. 나중에 GitHub Actions가 인증서 저장소를 복호화할 때 사용하므로 정확히 기억해 두세요.
fastlane match appstore
모든 과정이 정상적으로 완료되었다면 다음과 같은 화면이 나타납니다:
[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌
GitHub 및 권한 설정과 관련하여 문제가 발생했다면, 다음 포스트가 git 인증 토큰 생성에 도움이 될 것입니다.
생성된 인증서와 프로비저닝 프로필은 인증서 저장소 리소스로 업로드됩니다.

마지막으로, 다음을 여세요 project Xcode에서 앱의 release configuration용 provisioning profile을 업데이트합니다.

참고 사항 💡
MATCH
CI/CD가 인증서와 provisioning profile을 가져오려면 인증서 저장소에 접근할 수 있어야 합니다. 프라이빗 저장소에 접근하거나 읽을 수 있는 권한을 가진 personal access token을 생성하여 설정할 수 있습니다.
GitHub에서 다음으로 이동하세요. 설정 → 개발자 설정 → 개인 액세스 토큰 → 클릭 Generate New Token → 체크 repo scope 선택 → 클릭 Generate token.

생성된 personal access token을 복사해 두세요. 나중에 환경 변수로 사용됩니다. GIT_TOKEN.
그 다음 Fastlane 폴더에 생성된 match 파일을 Matchfile로 교체하세요.
CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]
git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)
GitHub Actions에서 인증서와 provisioning profile을 가져올 때 사용됩니다. 파일에 직접 입력하는 대신 GitHub Secrets에 변수를 설정하여 사용합니다.
빌드 처리 중
GitHub Actions에서, 비용은 분 단위로 청구됩니다 CI/CD 워크플로우 실행에 사용했습니다. 경험상 App Store Connect에서 빌드가 처리되기까지 약 10~15분 정도 소요됩니다.
비공개 프로젝트의 경우, 빌드당 예상 비용은 최대 $0.08/분 x 15분 = $1.2까지 발생할 수 있으며, 프로젝트 설정이나 의존성에 따라 더 높아질 수 있습니다.
개인 프로젝트의 비용 문제로 저와 같은 고민을 하고 계신다면, skip_waiting_for_build_processing 대상 true.
단점은 무엇일까요? 빌드가 완료된 후, 사용자에게 배포하려면 App Store Connect에서 앱의 규정 준수 사항을 수동으로 업데이트해야 합니다.
이것은 프라이빗 프로젝트의 빌드 시간을 절약하고 싶을 때 선택할 수 있는 옵션입니다. 무료 프로젝트라면 전혀 문제될 것이 없습니다. 다음을 확인하세요. 가격 정책.
3. GitHub Actions 설정하기
GitHub secrets 구성하기
다음 값들이 어디서 ENV 가져오는지 궁금하셨나요? 이제 비밀이 아닙니다. 바로 프로젝트의 secret에서 가져오는 것입니다. 🤦

1. APP_STORE_CONNECT_TEAM_ID - 여러 팀에 속해 있는 경우 App Store Connect 팀 ID를 입력하세요.
2. DEVELOPER_APP_ID - App Store Connect에서 앱으로 이동 → 앱 정보 → 아래로 스크롤하여 General Information 앱 섹션에서 다음 항목을 확인하세요: Apple ID.
3. DEVELOPER_APP_IDENTIFIER - 앱의 bundle identifier.
4. DEVELOPER_PORTAL_TEAM_ID - 여러 팀에 속해 있다면 Developer Portal 팀 ID.
5. FASTLANE_APPLE_ID - 앱 관리용 Apple ID 또는 개발자 이메일.
6. GIT_USERNAME & GIT_TOKEN - git username 및 personal access token.
7. MATCH_PASSWORD - match 초기화 시 설정한 passphrase (인증서 및 provisioning profile 복호화에 사용됨).
8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>, 예: match AppStore com.domain.blabla.demo.
9. TEMP_KEYCHAIN_USER & TEMP_KEYCHAIN_PASSWORD - 워크플로우용 임시 keychain 사용자 및 비밀번호를 할당합니다.
10. APPLE_KEY_ID — App Store Connect API Key 🔺Key ID.
11. APPLE_ISSUER_ID — App Store Connect API Key 🔺Issuer ID.
12. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 Key 파일 또는 Key 내용 .p8, 확인하세요
13. CERTIFICATE_STORE_URL — Match 키가 저장된 repo URL (예: https://github.com/***/fastlane_match.git)
4. 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: 2.7.2
- uses: maierj/fastlane-action@v2.3.0
env:
DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
with:
lane: closed_beta
- name: Upload release bundle
uses: actions/upload-artifact@v2
with:
name: ios-release
path: ./App.ipa
retention-days: 60
이 workflow는 각 GitHub 태그가 있습니다tag 자동화가 필요하면 다음을 참고하세요: GitHub actions를 이용한 자동 빌드 및 릴리스 먼저.
그다음 이 워크플로우가 NodeJS 의존성을 가져와 설치하고 JavaScript 앱을 빌드합니다.
새 커밋을 보낼 때마다 TestFlight에서 릴리스가 빌드됩니다.
앱은 Ionic을 사용할 필요가 없고 Capacitor 베이스만 필수입니다. 기존 Cordova 모듈이 있어도 되지만 Capacitor JS 플러그인을 사용하는 것이 좋습니다.
5. 워크플로우 트리거
커밋 생성
커밋을 만들면, 리포지토리에서 활성 워크플로우를 확인할 수 있습니다.
워크플로 트리거
브랜치에 새로운 커밋 푸시 main 또는 development 워크플로를 트리거하려면.

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

로컬 머신에서 배포 가능한가요?
네, 아주 간편하게 가능합니다.
프라이빗 저장소를 사용 중인데 무료 플랜의 사용 시간을 모두 소진했거나, 새 릴리스 비용을 지불하고 싶지 않거나, 혹은 수동으로 앱을 제출하는 것을 선호하는 상황을 가정해 봅시다.
시작해 봅시다
좋습니다, 먼저 다음을 생성해야 합니다 my_project_path/fastlane .env라는 이름의 파일을 .env, 다음과 동일한 경로에 Fastfile, 동일한 secret 속성을 생성할 수 있도록 _GitHub에 아래와 같이 작성합니다:
로컬 머신 배포용 .env 파일
이제, terminal 에서 다음을 실행하세요 Fastlane 로컬 환경에서 실행:
fastlane closed_beta
❌ 주의사항: .env 파일은 보안을 위해 노출되지 않도록 반드시 .gitignore에 추가해야 합니다. 예시: ❌
fastlane/*.env
원격 머신의 GitHub Actions에서 실행되는 것과 동일하게 로컬 머신에서도 작동해야 합니다. 🍻

터미널 실행: $ Fastlane closed_beta
여기까지 오셨다면 축하드립니다! 이제 Fastlane과 GitHub Actions를 통해 iOS 앱의 완전 자동화 프로세스를 구축하셨습니다.
새로운 커밋을 푸시할 때마다 Google Play console의 beta 채널에 릴리스가 빌드됩니다. 피드백을 주시면 블로그 내용을 개선하겠습니다. 질문이나 제안 사항이 있다면 이메일로 알려주세요. martin@capgo.app
내 기기에서 빌드하기
여전히 기기에서 직접 빌드해야 하는 경우, 프로비저닝에 수동으로 추가해야 합니다.
기기를 Mac에 연결하고 기기 메뉴를 여세요.
그 다음 식별자(identifier)를 복사하세요
이제 다음 명령어를 실행하세요:
fastlane register_new_device
기기 이름과 식별자를 설정하라는 메시지가 표시됩니다:

문제가 발생한다면
개발용 기기에서 테스트가 안 되는 등의 문제가 있다면, 보통 이 방법으로 해결됩니다.
문제를 해결해 줄 마법 같은 명령어가 있습니다:
fastlane match nuke development
fastlane match development
그 다음 : Shift(⇧)+Command(⌘)+K를 누르거나 Product > Clean(또는 “Clean Build Folder”)을 선택하여 프로젝트를 클린하세요.
그 다음 기기에서 앱을 다시 실행해 보세요.
감사합니다
이 블로그 포스트는 다음 문서들을 바탕으로 작성되었습니다:
match를 이용한 Automatic Capacitor IOS build with GitHub actions 이어보기
사용 중이라면 match를 활용해 GitHub Actions로 자동 Capacitor iOS 빌드 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의 구현 세부 사항을 참고하세요.