Published on

Flutter iOS 빌드 실패 - Podfile.lock 충돌 해결 가이드

Authors

서론

Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install 단계에서 멈추거나, Xcode 빌드가 CocoaPods could not find compatible versions 류의 메시지로 실패하는 경우가 있습니다. 특히 팀 개발/CI 환경에서 자주 등장하는 원인이 Podfile.lock 충돌입니다. 겉으로는 “pods 버전이 안 맞는다”처럼 보이지만, 실제로는 Flutter 플러그인 버전, CocoaPods 리포 스펙, Ruby/CocoaPods 버전, iOS deployment target, Xcode/SDK가 얽히면서 Podfile.lock이 일관성을 잃는 케이스가 많습니다.

이 글에서는 Podfile.lock이 무엇을 잠그는지부터, 충돌이 생기는 대표 패턴, 그리고 로컬/CI에서 안전하게 해결하는 절차와 재발 방지 체크리스트까지 정리합니다. iOS 서명/코드사인 이슈까지 함께 겪고 있다면 기존 글인 Flutter iOS 빌드 실패? Signing·CocoaPods 10분 해결도 같이 보면 문제 범위를 빠르게 좁힐 수 있습니다.

Podfile.lock 충돌이란 무엇인가

CocoaPods에서 Podfile.lock현재 프로젝트가 의존하는 Pod들의 정확한 버전과 해시(체크섬), spec repo 정보를 고정합니다. 즉, 같은 Podfile이라도 Podfile.lock이 다르면 설치되는 Pod 버전이 달라질 수 있고, 반대로 Podfile.lock은 “이 버전으로 설치해야 한다”는 강한 제약이 됩니다.

Flutter iOS 프로젝트는 보통 ios/Podfile에서 flutter_install_all_ios_pods(또는 유사 스크립트)를 통해 플러그인 Pod들을 동적으로 구성합니다. 이때 다음이 바뀌면 Podfile.lock과 현실이 어긋나기 쉽습니다.

  • Flutter 플러그인 버전 변경(예: Firebase 계열, permission_handler 등)
  • iOS 최소 버전 변경(예: iOS 11 → 12)
  • Xcode 업그레이드로 인한 SDK 변화
  • CocoaPods 버전 차이(1.11.x vs 1.15.x)
  • Spec repo 업데이트 여부(pod repo update)
  • use_frameworks!, use_modular_headers! 같은 설정 변경

결과적으로 pod install이 다음처럼 실패합니다.

  • CocoaPods could not find compatible versions for pod "XYZ"
  • The dependency 'ABC' is not used in any concrete target.
  • Specs satisfying the 'XYZ (= 1.2.3)' dependency were found, but they required a higher minimum deployment target.
  • CI에서만 실패(로컬에서는 성공): lockfile이 특정 머신의 CocoaPods/Repo 상태에 종속됨

가장 흔한 충돌 시나리오 5가지

1) 플러그인 업데이트로 Pod 버전 제약이 바뀜

pubspec.lock에서 플러그인이 업데이트되면, iOS 쪽 Pod 의존성이 바뀌는 경우가 많습니다. 그런데 팀원이 기존 Podfile.lock을 그대로 커밋해두면 다른 개발자/CI가 pod install 시 lockfile에 맞추려다가 실패합니다.

2) iOS deployment target이 낮아서 lockfile 요구를 못 맞춤

예를 들어 lockfile은 Firebase/CoreOnly 10.x를 잡고 있는데, 해당 버전이 iOS 12 이상을 요구하면 iOS 11 타겟에서는 충돌이 납니다.

3) CocoaPods 버전 차이로 해석이 달라짐

CocoaPods는 버전이 올라가며 resolver가 달라지고, Podfile.lock 포맷/체크섬 처리도 영향을 받을 수 있습니다. 로컬은 1.15, CI는 1.11이면 “같은 lockfile”이더라도 설치 결과가 달라질 수 있습니다.

4) Spec repo가 오래되어 원하는 Pod 버전을 못 찾음

pod install은 spec repo에서 버전을 찾습니다. repo가 오래되면 lockfile이 요구하는 버전이 있어도 “없다”고 판단합니다.

5) 멀티 타겟/Runner 설정 변경으로 lockfile이 꼬임

Runner 외에 Notification Service Extension 등을 추가하면, Pod 타겟 구성이 바뀌고 lockfile이 재생성되어야 합니다.

빠른 진단: 무엇이 충돌하는지 확인하는 법

아래 명령으로 어떤 Pod가 문제인지 먼저 좁힙니다.

cd ios
pod install --verbose

그리고 lockfile과 Podfile 설정을 함께 확인합니다.

  • ios/Podfile.lock에서 문제 Pod의 버전이 무엇인지
  • ios/Podfile에서 iOS 최소버전/프레임워크 설정이 무엇인지
  • pubspec.lock에서 플러그인 버전이 최근 바뀌었는지

추가로 CocoaPods 환경 자체를 확인합니다.

pod --version
ruby -v
xcodebuild -version

CI에서만 재현된다면, CI에서 위 버전들이 로컬과 동일한지 비교하는 것이 1순위입니다.

해결 전략: “잠금(lock)”을 어디까지 신뢰할 것인가

Podfile.lock 충돌을 해결하는 방식은 크게 두 가지입니다.

  1. lockfile을 신뢰하고, 환경(spec repo, deployment target, CocoaPods 버전)을 lockfile에 맞춘다
  2. lockfile을 재생성해서 현재 Flutter/플러그인 상태에 맞게 잠금을 새로 건다

일반적으로 Flutter 프로젝트는 플러그인 의존성이 자주 변하므로, 충돌이 발생했다면 재생성(2) 이 정답인 경우가 많습니다. 다만 팀/CI 일관성을 위해 재생성 후에는 반드시 lockfile을 커밋하고, CI도 그 lockfile을 기준으로 설치하도록 맞추는 것이 좋습니다.

로컬에서 10분 해결: 안전한 재생성 절차

아래 순서가 가장 실패율이 낮습니다.

1) Flutter 아티팩트 정리

flutter clean
rm -rf ~/.pub-cache/hosted
flutter pub get

~/.pub-cache까지 지우는 건 과한 경우도 있지만, 플러그인 캐시가 꼬인 상황에서는 효과가 큽니다(특히 브랜치 이동을 자주 하는 팀).

2) iOS Pods 완전 정리 후 재설치

cd ios
rm -rf Pods
rm -f Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
pod repo update
pod install
cd ..
  • Pods/Podfile.lock을 함께 지워야 “새 resolver 결과”가 반영됩니다.
  • pod repo update는 spec repo 미스매치 문제를 제거합니다.

3) Xcode에서 빌드 확인

open ios/Runner.xcworkspace

반드시 .xcworkspace를 열어 빌드합니다. .xcodeproj로 열면 Pods가 링크되지 않아 다른 오류로 보일 수 있습니다.

iOS 최소버전(iOS deployment target) 충돌 해결

많은 충돌이 “Pod가 더 높은 iOS 버전을 요구”해서 발생합니다. 이 경우 Podfile에서 플랫폼 버전을 올려야 합니다.

ios/Podfile 예시:

platform :ios, '12.0'

# Flutter default
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # 일부 Pod가 낮은 타겟으로 잡히는 것을 방지
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
    end
  end
end

여기서 중요한 포인트:

  • platform :ios, '12.0'만 올려도 해결되는 경우가 많지만,
  • 특정 Pod 타겟이 여전히 낮게 잡히는 케이스가 있어 post_install에서 강제하는 방식이 실무에서 자주 쓰입니다.

단, iOS 최소버전을 올리면 지원 기기 범위가 줄어드니 앱 정책과 맞는지 확인해야 합니다.

CI에서만 터지는 Podfile.lock 충돌: 재현성과 고정 전략

CI에서 pod install이 불안정하면, 보통 다음 중 하나입니다.

  • CocoaPods 버전이 로컬과 다름
  • Spec repo 업데이트가 매번 달라짐
  • 캐시 전략이 lockfile과 충돌

CocoaPods 버전 고정(권장)

Bundler로 CocoaPods 버전을 고정하면 팀/CI 일관성이 크게 올라갑니다.

ios/Gemfile 예시:

source 'https://rubygems.org'

gem 'cocoapods', '1.15.2'

설치/실행:

cd ios
bundle install
bundle exec pod install

CI에서도 bundle exec pod install을 사용하세요.

Spec repo 업데이트를 통제

매 빌드마다 pod repo update를 하면 최신 스펙을 받아오지만, 반대로 “오늘은 되고 내일은 안 되는” 변동성이 생길 수 있습니다. lockfile을 신뢰하는 전략이라면 보통은:

  • 기본은 pod install만 수행
  • 필요할 때만 pod repo update 또는 pod install --repo-update

처럼 운영하는 편이 안정적입니다.

캐시를 쓴다면 “키”를 lockfile 기반으로

GitHub Actions/GitLab CI에서 Pods/ 캐시를 쓰는 경우, 캐시 키를 Podfile.lock 해시로 잡지 않으면 충돌이 계속 반복됩니다. CI 권한/인증 이슈까지 겹치면 디버깅이 길어지니, CI 자체 트러블슈팅 경험이 있다면 GitHub Actions OIDC로 AWS 배포 권한 오류 해결처럼 “환경 차이에서 오는 실패”를 구조적으로 줄이는 접근이 도움이 됩니다.

팀 개발에서의 정답: Podfile.lock을 커밋할까?

결론부터 말하면 대부분의 앱 프로젝트는 ios/Podfile.lock을 커밋하는 것이 좋습니다.

  • 장점: 팀원/CI가 동일한 Pod 버전을 설치 → 재현성 증가
  • 단점: 플러그인 업데이트 시 lockfile 충돌이 자주 발생 → 하지만 이건 “변경을 명시적으로 관리”하는 비용

다만 다음 조건이면 예외를 고려할 수 있습니다.

  • 라이브러리/SDK 형태로 배포하며, 소비자 프로젝트에서 Pods를 해결해야 하는 경우
  • 내부 규칙상 iOS 의존성을 lock하지 않는 경우

일반적인 Flutter 앱(서비스 앱)이라면 커밋이 정석입니다.

자주 묻는 에러 메시지별 처방전

Specs satisfying the ... were found, but they required a higher minimum deployment target.

  • platform :ios 올리기
  • post_installIPHONEOS_DEPLOYMENT_TARGET 강제
  • 그래도 안 되면 플러그인/Pod 버전 다운그레이드(최후 수단)

CocoaPods could not find compatible versions for pod ...

  • Podfile.lock 삭제 후 재생성
  • pod repo update 또는 pod install --repo-update
  • CocoaPods 버전 고정(Bundler)

The sandbox is not in sync with the Podfile.lock.

  • pod install 재실행
  • Pods/ 삭제 후 pod install

재발 방지 체크리스트

  • pod --version을 팀/CI에서 고정(Bundler 권장)
  • Flutter 플러그인 업데이트 시:
    • flutter pub get 후 iOS에서 pod install까지 수행
    • 변경된 Podfile.lock을 함께 커밋
  • iOS 최소버전 변경은 Podfile, Xcode 프로젝트 설정, 그리고 필요 시 post_install까지 일관되게 반영
  • CI 캐시 키는 Podfile.lock 해시 기반으로 설계

마무리

Podfile.lock 충돌은 단순히 파일 하나의 문제가 아니라, Flutter 플러그인 생태계 + CocoaPods 해석기 + iOS 타겟 설정 + 팀/CI 환경 차이가 만나면서 발생하는 전형적인 “재현성 문제”입니다. 해결의 핵심은 두 가지입니다.

  1. 충돌이 나면 과감하게 Pods/lockfile을 정리하고 “현재 상태에 맞게” 재생성한다
  2. 재생성한 결과를 팀과 CI가 동일하게 따라가도록(버전 고정/캐시 키/설정) 시스템을 맞춘다

위 절차대로 해도 여전히 빌드가 실패한다면, Pod 충돌이 아니라 서명/프로비저닝/빌드 설정 문제일 가능성도 있으니 Flutter iOS 빌드 실패? Signing·CocoaPods 10분 해결에서 체크 범위를 확장해보는 것을 권합니다.