- Published on
Flutter iOS 빌드 실패 - CocoaPods·Podfile.lock 충돌 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install 단계에서 멈추거나, Xcode 빌드가 Podfile.lock/Pods/Manifest.lock 불일치로 실패하는 경우가 흔합니다. 특히 팀 개발(여러 macOS/여러 Xcode), CI 환경(캐시), Flutter 플러그인 업데이트가 겹치면 CocoaPods 해석 결과가 달라지면서 충돌이 터집니다.
이 글은 “왜 잠금 파일이 충돌하는지”를 먼저 구조적으로 설명하고, 로컬/CI에서 재현 가능하게 해결하는 순서(최소 파괴 → 완전 초기화), 그리고 장기적으로 충돌을 줄이는 운영 팁(버전 고정, Ruby/CocoaPods 관리, Pod repo 관리)을 정리합니다. 빌드 문제를 ‘감’이 아니라 ‘절차’로 다루는 방식은 다른 장애 추적에도 그대로 적용됩니다. 예를 들어 빌드/배포가 느려지거나 실패할 때 원인 분해하는 방법론은 Next.js 14 빌드 OOM·느려짐 해결 - SWC 캐시·메모리 튜닝 같은 글과도 결이 같습니다.
증상 패턴: 어떤 에러가 나오면 Podfile.lock을 의심해야 하나
다음 중 하나라도 보이면 1차로 Podfile.lock 충돌/불일치를 의심합니다.
The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.error: The sandbox is not in sync with the Podfile.lock. (Pods/Manifest.lock)CocoaPods could not find compatible versions for pod ...(특히 특정 플러그인 업데이트 직후)Specs satisfying the ... dependency were found, but they required a higher minimum deployment target.pod install은 성공했는데 Xcode에서framework not found/module not found가 발생(잠금은 맞지만 설정이 꼬인 경우)
핵심은 iOS 빌드가 “Flutter 프로젝트”가 아니라 “iOS/Pods로 내려간 네이티브 의존성 그래프”를 함께 갖고 있다는 점입니다. 이 그래프의 단일 진실 소스가 ios/Podfile과 ios/Podfile.lock이고, 실제 설치 결과가 ios/Pods/Manifest.lock입니다.
CocoaPods 잠금 파일이 충돌하는 원리 (Podfile.lock vs Manifest.lock)
Podfile.lock: 의존성 해석 결과(버전 고정). 팀/CI에서 동일한 결과를 재현하기 위한 파일이라 보통 Git에 커밋합니다.Pods/Manifest.lock:pod install이 끝난 시점의 실제 설치 결과 스냅샷.
빌드 시 CocoaPods 스크립트는 두 파일이 동일한지 비교합니다. 다르면 “샌드박스가 동기화되지 않았다”고 판단하고 실패합니다.
왜 달라질까요?
- 누군가
Podfile.lock을 변경했는데, 다른 사람이pod install을 안 함 Pods/디렉터리 또는 DerivedData가 꼬여서 Manifest만 갱신/유실- CocoaPods/Ruby 버전 차이로 해석 결과가 달라져 lock이 변경됨
pod repo update/spec repo 상태 차이로 해석 결과가 달라짐- Flutter 플러그인(예: Firebase, GoogleMaps 등)이 내부적으로 iOS pod 버전 제약을 바꾸면서 충돌
이제부터는 “원인별로 어떤 명령을 어떤 순서로” 적용할지 정리합니다.
해결 전략: 최소 파괴 → 완전 초기화 순서로 접근
실무에서는 무조건 rm -rf ios/Pods부터 치면 되긴 하지만, CI 캐시/팀 규칙/시간 비용이 큽니다. 아래 순서대로 진행하면 대부분의 케이스를 안전하게 해결할 수 있습니다.
1단계: 현재 상태 확인(가장 흔한 실수 찾기)
cd ios
ls -al Podfile Podfile.lock
ls -al Pods/Manifest.lock
# lock 파일 차이 확인
if [ -f Pods/Manifest.lock ]; then
diff -u Podfile.lock Pods/Manifest.lock || true
fi
Podfile.lock은 있는데Pods/Manifest.lock이 없으면:Pods/가 깨졌거나 설치가 덜 된 상태- diff가 크면: 누군가 lock을 바꿨거나(커밋), 설치 결과가 달라진 것
2단계: 가장 안전한 동기화(설치만 다시)
cd ios
pod install
- 대부분 여기서 해결됩니다.
- 실패한다면 CocoaPods 버전/레포/플러그인 버전 충돌 가능성이 큽니다.
3단계: Flutter 쪽 캐시/생성물 정리 후 재설치
Flutter 플러그인 목록이 바뀐 직후라면 iOS 쪽만 만져서는 해결이 안 될 수 있습니다.
# 프로젝트 루트에서
flutter clean
rm -rf ios/Pods ios/.symlinks ios/Flutter/Flutter.framework ios/Flutter/Flutter.podspec
flutter pub get
cd ios
pod install
ios/.symlinks는 Flutter 플러그인이 iOS에 연결되는 심볼릭 링크 영역이라, 여기 꼬이면 “모듈을 못 찾는” 류의 에러가 납니다.
4단계: 완전 초기화(레포/캐시까지 리셋)
pod install이 의존성 해석 단계에서 계속 깨지면, 로컬 spec repo 상태나 CocoaPods 캐시가 원인일 수 있습니다.
cd ios
pod deintegrate
pod cache clean --all
rm -rf Pods Podfile.lock
# spec repo 갱신(필요 시)
pod repo update
pod install
주의:
Podfile.lock을 삭제하면 버전이 떠버릴 수 있습니다(최신으로 올라감). 팀 프로젝트라면 이 단계는 “최후의 수단”으로 두고, 가능하면 lock을 유지한 채 해결하세요.
자주 터지는 충돌 케이스별 처방
케이스 A: “CocoaPods could not find compatible versions”
대개 플러그인이 요구하는 pod 버전이 서로 맞지 않거나, iOS 최소 버전이 낮아서 생깁니다.
- iOS Deployment Target 상향
ios/Podfile에서:
platform :ios, '13.0'
그리고:
cd ios
pod install
- 특정 Pod 버전 고정(임시 봉합)
# Podfile
pod 'Firebase/CoreOnly', '10.25.0'
이 방식은 팀 전체에 영향을 주므로, “왜 고정했는지”를 주석으로 남기고 추후 제거 계획을 잡는 게 좋습니다.
케이스 B: “sandbox is not in sync”가 CI에서만 발생
CI는 캐시(특히 Pods/나 CocoaPods cache)를 쓰다가 lock이 바뀌면 바로 깨집니다.
권장 접근:
Podfile.lock은 반드시 커밋- CI에서
pod install --deployment사용
cd ios
pod install --deployment
--deployment는 lock과 설치 결과가 다르면 실패시켜 “조용히 업데이트되어 lock이 바뀌는 상황”을 막습니다. 즉, CI가 문제를 숨기지 않고 빨리 알려줍니다.
또한 캐시를 쓰더라도 키를 Podfile.lock 해시로 묶어 lock이 바뀌면 캐시가 무효화되게 설정하세요.
케이스 C: Apple Silicon(M1/M2) + Ruby/CocoaPods 이슈
ffi 같은 네이티브 gem 문제로 pod 실행 자체가 깨지는 경우가 있습니다. 팀 내에서 Ruby/CocoaPods 버전이 제각각이면 lock 해석 결과도 달라질 수 있어, 장기적으로는 “도구 버전 고정”이 핵심입니다.
Gemfile로 CocoaPods 버전 고정 권장
# ios/Gemfile
source 'https://rubygems.org'
gem 'cocoapods', '1.15.2'
cd ios
bundle install
bundle exec pod install
이렇게 하면 “내 로컬은 pod 1.14, 다른 사람은 1.15” 같은 변수를 크게 줄입니다.
권장 운영 규칙: 충돌을 ‘예방’하는 팀 세팅
1) Podfile.lock은 커밋하되, 업데이트 절차를 정한다
- Flutter 플러그인 업데이트(PR)에는
Podfile.lock변경이 같이 들어오게 하세요. - PR에서 iOS 빌드가 통과했는지 확인(로컬만 믿지 말 것).
2) CocoaPods 실행 방식을 통일한다
- 로컬/CI 모두
bundle exec pod install로 통일 - CI는
pod install --deployment
3) 문제 재발 시 “원인 추적 로그”를 남긴다
빌드 실패는 단발로 끝나지 않습니다. 재발하면 시간만 잡아먹습니다. 아래 로그를 이슈/PR에 남겨두면 다음 디버깅이 빨라집니다.
ruby -v
pod --version
flutter --version
xcodebuild -version
cd ios && pod env
장애를 구조적으로 추적하는 접근은 쿠버네티스/서버 운영에서도 동일합니다. 예를 들어 반복 재시작을 로그와 상태로 쪼개 원인을 좁히는 방식은 Kubernetes CrashLoopBackOff 원인별 로그·Probe·리소스 디버깅 같은 문제에서도 그대로 통합니다.
실전 체크리스트: “이 순서대로 하면 대부분 끝난다”
아래는 현장에서 가장 재현성 좋았던 순서입니다.
- Flutter 의존성 정합성 확보
flutter pub get
flutter clean
- iOS Pods 동기화
cd ios
bundle exec pod install || pod install
- 그래도 실패하면 iOS 생성물 초기화
rm -rf Pods .symlinks
pod deintegrate
bundle exec pod install || pod install
- 마지막 수단: lock 재생성(팀 합의 후)
rm -f Podfile.lock
bundle exec pod install || pod install
마무리
Flutter iOS 빌드 실패의 상당수는 “CocoaPods가 나쁘다”가 아니라, 잠금 파일과 실제 설치 결과가 어긋나거나(동기화 문제), 팀/CI의 도구 버전이 달라 의존성 해석이 달라지는(재현성 문제) 데서 시작합니다.
핵심은 다음 두 가지입니다.
- Podfile.lock을 단일 진실 소스로 유지하고, CI에서는
--deployment로 드리프트를 막기 - CocoaPods 실행 환경을 고정(Gemfile + bundle exec)해서 사람/머신마다 다른 결과가 나오지 않게 하기
이 원칙만 지켜도 “갑자기 iOS만 빌드가 깨지는 날”이 확실히 줄어듭니다.