본문 바로가기

Capacitor를 위한 CI/CD: 흔한 실수와 해결 방법

Capacitor 빌드를 망치는 CI/CD 실수들: iOS signing, macOS runner, Xcode 26, stale cap sync, 버전 코드, 스토어 거절 사례와 각각의 해결책을 알아봅니다.

Capacitor를 위한 CI/CD: 흔한 실수와 해결 방법

대부분의 Capacitor CI/CD 실패는 몇 가지 공통된 원인에서 발생합니다. iOS code signing, 누락되거나 오래된 macOS 툴링, 네이티브 프로젝트에 반영되지 않은 웹 빌드, 재사용된 빌드 번호, 그리고 업로드 시점에야 발견되는 스토어 요구사항 등이 대표적입니다. 각 문제는 원인이 명확하며 한 번만 해결하면 됩니다. 이 가이드에서는 파이프라인 단계별로 실수를 분류하고, 발생하는 에러와 원인, 그리고 수정 방법을 정리했습니다.

파이프라인을 처음부터 구축 중이라면, 다음을 읽어보세요: Capacitor 앱을 위한 CI/CD 설정하기 먼저 설정한 다음, 이 목록으로 보안을 강화하세요.

1단계: 웹 레이어 빌드

함정: 네이티브 앱에 이전 버전의 웹 빌드가 포함됨

증상: CI는 성공하고 앱도 설치되지만, UI는 어제 버전으로 보임.

원인: cap sync 내용을 그대로 복사합니다 webDir 그 시점에. 만약 파이프라인이 cap sync 웹 빌드 전 단계에서, 또는 웹 빌드가 다음 폴더와 다른 곳에 결과물을 작성하는 경우 webDir in capacitor.config.ts네이티브 프로젝트에 오래된 파일이 포함됩니다.

해결 방법: 항상 이 순서대로 단계를 실행하고, 출력 폴더가 비어 있으면 실패 처리하세요.

bun install --frozen-lockfile
bun run build
test -f dist/index.html || { echo "web build missing"; exit 1; }
bunx cap sync

반드시 webDir 번들러 출력 경로와 일치하는지 확인하세요 (dist Vite의 경우, www Ionic 기반 Angular의 경우, build 일부 React 설정의 경우).

주의 사항: 설정에 dev server URL이 남아 있는 경우

증상: 릴리스 빌드에서 빈 화면이 나타나거나 로드하려고 시도합니다 http://192.168.x.x:5173.

원인: server.url 에서 capacitor.config.ts live reload용으로 설정된 채 커밋되었습니다.

해결 방법: 절대 커밋하지 마세요 server.url. CI에서 설정하지 않는 환경 변수로부터 읽어오세요:

import type { CapacitorConfig } from '@capacitor/cli'

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'Example',
  webDir: 'dist',
  ...(process.env.LIVE_RELOAD_URL && {
    server: { url: process.env.LIVE_RELOAD_URL, cleartext: true },
  }),
}

export default config

함정: 잘못된 시점에 포함된 환경 변수

증상: 운영 앱이 스테이징 API와 통신합니다.

원인: Vite, webpack, Angular는 빌드 시점에 환경 변수를 인라인화합니다. 빌드가 실행될 때 bun run build 값이 포함되어 있으면, 해당 바이너리와 동일한 작업으로 빌드된 모든 live update에 그 값이 적용됩니다.

해결 방법: 웹 빌드 전, 작업별로 환경별 변수를 설정하고 스테이징과 운영용 번들을 각각 빌드하세요. 빌드 후 교체하려고 하지 마세요. cap sync.

주의사항: lockfile 불일치

증상: CI 환경의 플러그인 버전이 로컬과 달라 심볼 누락으로 인한 native compilation 오류가 발생할 수 있습니다.

해결 방법: lockfile을 커밋하고 다음 명령어로 설치하세요: bun install --frozen-lockfile (또는 npm ci)를 사용하여 @capacitor/core, @capacitor/ios, @capacitor/android 및 @capacitor/cli 버전을 동일하게 고정하세요. 버전 불일치는 native 에러의 주요 원인입니다. 자세한 내용은 Capacitor 버전 불일치 오류 해결하기.

2단계: native 툴체인

주의사항: Node, JDK 또는 Xcode 버전 오류

증상: Unsupported class file major version, The engine "node" is incompatible, 또는 SDK 기능 관련 Xcode 에러.

원인: 호스팅 러너 이미지가 변경되었으며, Capacitor 8은 엄격한 최소 사양을 요구합니다.

도구 Capacitor 8 요구 사항
Node.js 22 이상
JDK 21
Xcode 26 이상
iOS 배포 대상 버전 15.0
Android minSdkVersion / targetSdkVersion 24 / 36

해결 방법: 신뢰하지 말고 파이프라인에서 모든 버전을 명시적으로 고정하세요 latest:

- uses: actions/setup-node@v6
  with:
    node-version-file: .nvmrc
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '21'
- uses: maxim-lobanov/setup-xcode@v1
  with:
    xcode-version: '26'

함정: Apple이 더 이상 허용하지 않는 Xcode로 빌드하기

증상: 업로드가 실패하고, 지원되지 않는 SDK로 앱이 빌드되었다는 메시지가 표시됩니다.

원인: 2026년 4월 28일부터 App Store Connect는 Xcode 26과 iOS 26 SDK를 요구합니다. 셀프 호스팅 Mac과 오래된 runner 이미지는 여전히 Xcode 16을 기본으로 사용합니다.

해결 방법: 다음 중 하나를 선택하세요 macos-26 이미지를 선택하거나 runner에 Xcode 26을 설치하세요. 자세한 내용은 Capacitor 앱을 위한 Apple의 Xcode 26 요구 사항.

주의사항: CocoaPods와 SPM 혼동

증상: xcodebuild: error: 'App.xcworkspace' does not exist, 또는 pods를 찾을 수 없음.

원인: 새로운 Capacitor 8 프로젝트는 Swift Package Manager를 사용하며 build 시 ios/App/App.xcodeproj. 기존 프로젝트는 CocoaPods를 사용하며 build 시 ios/App/App.xcworkspace 후에 pod install. 이전 튜토리얼에서 복사한 Pipeline은 workspace를 가정합니다.

해결 방법: 프로젝트가 어떤 것을 사용하는지 확인하고 올바른 파일을 build 하세요. 마이그레이션 중이라면 Capacitor 앱을 SPM으로 마이그레이션하는 방법.

주의사항: Gradle 업그레이드 후 Android plugin build 오류

증상: Namespace not specified, package attribute is deprecated, 또는 플러그인에서 발생하는 오류 build.gradle Android Studio 업데이트 이후.

해결 방법: Android Gradle Plugin 버전을 고정하세요. android/build.gradle, 브랜치에서 업그레이드한 뒤 플러그인을 먼저 업데이트하세요. 자세한 오류는 다음에서 확인 가능합니다. AGP 9 환경에서 Capacitor 플러그인 빌드 오류 해결하기.

3단계: Code 서명(signing)

주의사항: iOS 서명이 로컬 Mac에서만 작동하는 경우

증상: No signing certificate "iOS Distribution" found, No profiles for 'com.example.app' were found, 또는 errSecInternalComponent.

원인: Mac의 키체인에는 인증서가 저장되어 있고 Xcode가 프로파일을 자동으로 다운로드합니다. 하지만 CI 러너에는 인증서도 없고, 키체인은 비대화형 세션이라 잠겨 있습니다.

해결 방법: 인증서를 임시로 잠금 해제된 키체인에 가져오고, Xcode 16 이상 버전이 찾는 위치에 프로파일을 설치하세요:

security create-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security set-keychain-settings -lut 21600 ci.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security import dist.p12 -k ci.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" ci.keychain
security list-keychains -d user -s ci.keychain login.keychain

PROFILES="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
mkdir -p "$PROFILES"
cp app.mobileprovision "$PROFILES/"

레거시 방식이 아닌 Apple Distributionidentity name을 iOS Distribution사용하세요. fastlane의 setup_ci 기능과 match 자동화 기능을 활용하면 됩니다. Capgo Build는 인증서와 프로파일을 환경 변수로 받아 자체 머신에서 키체인 작업을 처리하므로, Linux 작업에서 직접 다룰 필요가 없습니다. security.

주의사항: 만료되거나 취소된 인증서

증상: 1년 동안 잘 작동하던 파이프라인이 하룻밤 사이에 실패합니다.

원인: Apple 배포 인증서와 프로비저닝 프로필은 1년마다 만료됩니다. 팀원이 Xcode에서 새 인증서를 생성하면 CI에서 사용하는 프로필이 무효화될 수도 있습니다.

해결 방법: 만료일을 팀 캘린더에 등록하고, CI용 공유 배포 인증서를 하나 유지하며, 빌드 전에 미리 확인하세요. bunx @capgo/cli@latest build prescan --platform ios 업로드 전 인증서 만료, 비밀번호, 프로필 페어링 상태를 미리 확인합니다.

주의사항: Apple ID 로그인 및 2단계 인증 프롬프트

증상: fastlane이 6자리 code를 기다리며 멈춰버립니다.

해결 방법: Apple ID와 비밀번호 대신 App Store Connect API 키 (.p8key ID, issuer ID 포함)를 사용하세요. 이 방식은 2단계 인증이 필요 없으며 App Manager 권한으로 범위를 제한할 수 있습니다. 올바른 키를 사용해도 인증에 실패한다면 러너(runner)의 시간을 확인하세요. 토큰은 로컬 시간으로 서명되는데, Apple은 타임스탬프가 맞지 않는 토큰을 거부합니다.

주의사항: 디코딩되지 않는 base64 secret

증상: MAC verification failed, invalid keystore format, 또는 base64: invalid input.

원인: base64 값을 복사할 때 줄 바꿈이 추가되었거나, .p12 macOS에서 읽을 수 없는 OpenSSL 3 기본 설정으로 생성된 경우입니다.

해결 방법: 한 줄로 인코딩하고, -legacy 생성 시 .p12 OpenSSL 3를 사용하여:

base64 -i dist.p12 | tr -d '\n' > dist.p12.b64
openssl pkcs12 -export -legacy -inkey key.pem -in cert.pem -out dist.p12

주의사항: Android keystore 분실

증상: keystore를 가진 사람이 아무도 없으면 업데이트에 서명할 수 없습니다.

해결 방법: with Play App Signing, the keystore is the upload key, and Play Console support can register a new one. Store the keystore in your CI secrets and in an offline backup. Never let it live only on one laptop. The Android keystore generator를 사용하면 처음 시작할 때 새 키를 생성할 수 있습니다.

4단계: 파이프라인 설계

함정: 모든 pull request마다 native build 실행

증상: PR 체크 속도가 느려지고 macOS 사용 비용이 크게 증가합니다.

원인: 호스팅된 macOS runner에서 진행하는 iOS build는 대부분의 CI 플랜에서 가장 비용이 많이 드는 작업이며, 대부분의 커밋은 JavaScript만 수정합니다.

해결 방법: run lint, tests, and the web build on every PR. Run native builds on release tags or merges to mainPR 프리뷰의 경우, 바이너리를 빌드하는 대신 Capgo에 설명된 대로 web bundle을 Capgo 채널로 전송하세요. Capacitor 앱을 위한 CI/CD 플랫폼 비교.

함정: iOS와 Android를 순차적으로 빌드하는 경우

해결 방법: matrix를 사용하여 두 플랫폼을 병렬로 빌드하고, fail-fast: false iOS 서명 문제가 정상적인 Android 빌드까지 중단시키지 않도록 설정하세요.

strategy:
  fail-fast: false
  matrix:
    platform: [ios, android]

함정: 캐싱을 사용하지 않거나 잘못된 캐시를 사용하는 경우

증상: 매 빌드마다 Gradle 의존성과 CocoaPods를 처음부터 다시 다운로드하거나, 스테이징 빌드에 오래된 캐시의 프로덕션 설정이 포함될 수 있습니다.

해결 방법: cache ~/.gradle/caches, ~/.gradle/wrapper, 그리고 ios/App/Pods lockfile을 기준으로 키를 지정하세요. 환경별로 캐시를 분리해야 합니다. Capgo Build를 사용하면 앱별 빌드 캐시를 다음과 같이 나눌 수 있습니다. --cache-key prod 및 --cache-key staging, 또는 건너뛰었습니다. --no-cache 클린 빌드를 위해.

주의사항: 모노레포 경로

증상: could not find capacitor.config 또는 네이티브 프로젝트에서 누락된 플러그인.

해결 방법: 앱 패키지에서 Capacitor 명령을 실행하고, 도구가 hoisted된 경로를 가리키도록 설정하세요. node_modules. Capgo CLI은(는) 이를 --path 및 --node-modules 지원합니다.

5단계: 스토어 제출

주의사항: 빌드 번호 재사용

증상: iOS의 “The bundle version must be higher than the previously uploaded version” 또는 Google Play의 “Version code has already been used” 오류가 발생합니다.

해결 방법: CI에서 번호를 생성하세요. fastlane을 사용하면 최신 TestFlight 빌드를 읽어 1을 더할 수 있습니다. Capgo Build를 사용하면 기본적으로 App Store Connect에서 최신 빌드 번호를 가져오거나 가장 높은 번호를 versionCode Google Play에서 가져와 자동으로 1을 증가시킵니다.

주의사항: TestFlight에 빌드가 멈춰 있는 경우

증상: 업로드는 성공했지만 테스터들에게 빌드가 보이지 않는 현상입니다.

원인: 수출 규정 준수(export compliance) 답변이 누락되었습니다.

해결 방법: 한 번만 선언하세요: ios/App/App/Info.plist 표준 암호화만 사용하는 경우:

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

주의: privacy manifest 거절 사례

증상: 필수 사유 API 선언 누락(ITMS-91053)에 대한 Apple의 이메일.

해결 방법: 다음 항목을 추가하세요: PrivacyInfo.xcprivacy 앱 타겟에 추가하고 자체 manifest를 포함하는 플러그인을 업데이트하세요. 다음을 참고하세요: iOS 앱을 위한 privacy manifest 가이드.

주의: 잘못된 아티팩트 유형

해결 방법: Google Play에는 AAB가 필요합니다 (bundleRelease), APK가 아닙니다. bundleRelease 여전히 성공하며, release 서명 설정이 누락되어 Play Store에서 거부되는 unsigned AAB가 생성됩니다. 따라서 먼저 설정을 완료하세요. signingConfigs.release 에서 android/app/build.gradle iOS는 개발용이나 ad hoc IPA가 아닌 App Store export가 필요합니다. 빌드 단계에서 export 방식을 확인하세요.

6단계: Live Updates

함정: 새로운 Native Build가 필요한 live update 배포

증상: OTA 업데이트 후, 설치된 바이너리에 존재하지 않는 플러그인 메서드를 호출하여 앱이 충돌합니다.

원인: 웹 번들이 사용자 기기에 컴파일된 앱의 플러그인 버전보다 더 높은 버전에 의존하는 경우입니다.

해결 방법: 파이프라인이 결정하도록 맡기세요. build needed 네이티브 의존성이 채널의 라이브 상태와 일치하면 0을, 새로운 바이너리가 필요하면 1을 반환합니다:

if bunx @capgo/cli@latest build needed com.example.app --channel production; then
  bunx @capgo/cli@latest bundle upload com.example.app --channel production
else
  bunx cap sync
  bunx @capgo/cli@latest build request com.example.app --platform ios
  bunx @capgo/cli@latest build request com.example.app --platform android
fi

또한 아래 파일들이 ios/, android/, 또는 capacitor.config.* 변경될 경우 네이티브 경로를 강제합니다. 전체 패턴은 live update 자동 선택 또는 Native Build, 호환성 규칙은 네이티브 호환성.

가장 많은 함정을 제거하는 해결책

If you only change one thing, move iOS compilation and signing out of your CI runners. Keychain setup, Xcode upgrades, macOS costs, and profile installation all disappear from your pipeline when a Linux job hands the prepared project to Capgo Build:

bun install --frozen-lockfile && bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release

1, 5, 6단계의 함정은 러너가 아닌 프로젝트 자체의 문제이므로 여전히 적용됩니다. 실패하는 작업의 디버깅에 대한 자세한 내용은 Capacitor CI/CD 파이프라인 빌드 오류 해결하기.

빠른 참조

에러 주의 사항 섹션
새 빌드에 이전 UI가 반영됨 cap sync 웹 빌드 전 1단계
No profiles for ... were found 프로파일 미설치 또는 불일치 3단계
errSecInternalComponent 키체인 잠김 3단계
MAC verification failed 비밀번호 오류 또는 OpenSSL 3 문제 .p12 3단계
Unsupported class file major version JDK 버전 불일치 2단계
업로드 시 SDK 버전이 너무 낮음 Xcode 버전이 26보다 낮음 2단계
Bundle 버전 상향 필요 빌드 번호 중복 사용 5단계
code 버전이 이미 사용됨 중복 사용됨 versionCode 5단계
live update 후 크래시 발생 OTA로 배포된 Native 변경 사항 6단계
Capacitor 앱을 위한 Live Updates

웹 레이어 버그가 발생했을 때, 앱 스토어 승인을 며칠씩 기다리는 대신 Capgo를 통해 즉시 수정하세요. Native 변경 사항은 기존 리뷰 절차를 따르되, 사용자는 백그라운드에서 즉시 업데이트를 받을 수 있습니다.

최신 블로그 소식

Capgo는 진정한 프로페셔널 모바일 앱을 제작하는 데 필요한 최적의 인사이트를 제공합니다.