Published on

Flutter iOS 빌드 실패 - Pod install 오류 해결 가이드

Authors

서론

Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install 단계에서 멈추거나, Xcode에서만 빌드가 깨지는 일이 흔합니다. 원인은 대체로 CocoaPods/ Ruby 환경, Podfile 설정, Xcode/SDK 버전 불일치, 캐시/DerivedData 오염, 아키텍처(특히 Apple Silicon), 네트워크/레포 접근 문제로 수렴합니다.

이 글은 “일단 되는 조합”을 찾는 방식이 아니라, 로그에서 원인을 빠르게 좁히고 가장 적은 변경으로 안정적으로 복구하는 절차를 제공합니다. (CI에서 반복 재현되는 경우까지 포함)

> 참고로, 빌드/배포 파이프라인에서 403/429/권한 같은 오류를 원인별로 분해하는 방식은 Kubernetes/CI 트러블슈팅과 유사합니다. 예: GitHub Actions GITHUB_TOKEN 403 권한 오류 해결


1) 증상별로 먼저 분류하기 (로그 키워드)

flutter build ios 또는 flutter run 중 iOS 단계에서 흔히 보이는 메시지들입니다.

  • CocoaPods could not find compatible versions for pod ...
  • Specs satisfying the ... dependency were found, but they required a higher minimum deployment target
  • pod installffi/native extension 컴파일 실패
  • xcodebuild에서 Framework not found / module not found
  • The sandbox is not in sync with the Podfile.lock
  • CDN: trunk URL couldn't be downloaded / SSL / HTTP2 관련 네트워크 오류
  • Apple Silicon에서 building for iOS Simulator, but linking in object file built for iOS 또는 arm64 관련 오류

이제부터는 “가장 재현성이 높은 해결 순서”로 진행합니다.


2) 가장 먼저 할 일: 클린업의 ‘정확한’ 순서

Pod 관련 문제는 캐시/락파일/워크스페이스가 꼬여 있는 경우가 많습니다. 아래는 부작용이 적고, 대부분의 케이스에서 안전한 초기화 절차입니다.

2.1 Flutter + iOS 폴더 정리

flutter clean
rm -rf ios/Pods ios/Podfile.lock ios/.symlinks
rm -rf ios/Flutter/Flutter.framework ios/Flutter/Flutter.podspec
rm -rf ~/Library/Developer/Xcode/DerivedData

2.2 Pod 재설치

cd ios
pod deintegrate
pod repo update
pod install --verbose
cd ..
  • pod repo update는 오래된 spec 때문에 버전 해석이 꼬일 때 특히 중요합니다.
  • --verbose는 “어느 pod에서 막히는지”를 명확히 보여줍니다.

3) Podfile에서 가장 자주 터지는 설정 5가지

3.1 iOS Deployment Target이 낮아서 깨지는 경우

로그 예:

  • required a higher minimum deployment target

해결: ios/Podfile의 플랫폼 버전을 올립니다.

platform :ios, '13.0'

# 또는 팀 정책에 따라 12.0/14.0 등

그리고 Xcode 프로젝트의 Runner 타겟에서도 iOS Deployment Target이 동일/이상인지 확인합니다.

3.2 use_frameworks! / use_modular_headers! 충돌

Firebase 계열, 일부 네이티브 SDK에서 충돌이 납니다. Flutter 플러그인 조합에 따라 정답이 달라서, 에러 메시지에 나온 pod 이름 기준으로 접근하는 게 좋습니다.

일반적인 안정 조합 중 하나:

use_frameworks! :linkage => :static
use_modular_headers!
  • :static은 “동적 프레임워크 링크 문제”를 줄여줍니다.
  • 단, 일부 Pod는 static linkage와 충돌할 수 있으니, 충돌 Pod가 특정되면 그 Pod만 예외 처리하는 방식도 고려합니다.

3.3 post_install에서 시뮬레이터 arm64 제외 설정

Apple Silicon에서 특정 바이너리 Pod가 시뮬레이터 arm64를 지원하지 않아 실패할 수 있습니다.

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
    end
  end
end
  • 이 설정은 “임시 봉합”에 가깝습니다. 가능하면 해당 Pod를 최신으로 올려 arm64 simulator 지원 여부를 확인하는 게 우선입니다.

3.4 Podfile.lock과 sandbox 불일치

로그 예:

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

해결:

cd ios
rm -rf Pods Podfile.lock
pod install

CI에서 자주 발생한다면, Podfile.lock을 커밋하고, pod install만 수행하도록 파이프라인을 고정하는 것이 재현성에 유리합니다.

플러그인 업데이트/브랜치 전환을 반복하면 .symlinks가 꼬이면서 pod install이 이상 동작할 수 있습니다.

  • 위의 클린업 절차(2.1)에서 .symlinks 제거가 핵심입니다.

4) Ruby/CocoaPods 환경 문제(가장 흔한 ‘근본 원인’)

pod install이 실패하는데 에러가 ffi, psych, json 같은 Ruby native extension 컴파일로 튄다면, Podfile 자체보다 로컬 Ruby/Pod 버전이 원인일 가능성이 큽니다.

4.1 CocoaPods 버전 확인

pod --version
ruby -v
which pod
which ruby

4.2 Bundler로 CocoaPods 버전 고정(권장)

팀/CI에서 재현성을 확보하려면 Gemfile로 CocoaPods를 고정하는 방식이 가장 깔끔합니다.

프로젝트 루트에 Gemfile 생성:

source 'https://rubygems.org'

gem 'cocoapods', '~> 1.15'

설치 및 실행:

bundle install
cd ios
bundle exec pod install
  • 로컬에 설치된 pod와 충돌하지 않고, 프로젝트 단위로 버전을 고정할 수 있습니다.

4.3 Apple Silicon(M1/M2/M3)에서 x86_64로 강제 실행이 필요한 경우

특정 오래된 gem/Pod가 arm64에서 설치가 깨질 때가 있습니다(요즘은 많이 줄었지만 레거시 플러그인에서 여전히 발생).

sudo arch -x86_64 gem install cocoapods
arch -x86_64 pod install

다만 이 방법은 “Rosetta 의존”이 생길 수 있으므로, 가능하면 Pod/gem을 최신으로 올려 arm64 네이티브로 해결하는 편이 좋습니다.


5) 네트워크/CDN 문제: trunk 접근 실패, SSL 오류

회사망/프록시/방화벽 환경에서 아래가 자주 보입니다.

  • CDN: trunk URL couldn't be downloaded
  • SSL_connect returned=1 errno=0 state=error: certificate verify failed

5.1 CocoaPods repo를 git 기반으로 전환(우회)

CDN이 막힐 때, specs repo를 git으로 쓰는 방식이 통합니다.

pod repo remove trunk
pod setup

또는 환경에 따라 --repo-update를 명시:

cd ios
pod install --repo-update

네트워크 이슈는 “레이트리밋/차단/권한”으로도 나타납니다. API/레지스트리에서 429가 뜨는 유형의 접근법(재시도, 토큰버킷, 캐시)도 참고할 만합니다: OpenAI Responses API 429 레이트리밋 토큰버킷으로 끝내기


6) Xcode/Command Line Tools 불일치로 인한 빌드 실패

Flutter는 내부적으로 xcodebuild를 호출합니다. Xcode를 업데이트했는데 CLI tools가 이전 버전을 가리키면, pod install은 되는데 빌드가 실패하거나 반대로 설치가 실패할 수 있습니다.

6.1 활성 Xcode 경로 확인 및 변경

xcode-select -p
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

6.2 라이선스/초기 설정

sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch

7) 플러그인/Pod 버전 충돌: 어떤 Pod가 문제인지 좁히는 법

pod install --verbose 로그에서 충돌 Pod 이름이 나오면, 다음 순서로 접근합니다.

  1. Flutter 플러그인 버전 업데이트 (pubspec.yaml)
  2. pod repo update 후 재시도
  3. 충돌 Pod의 최소 iOS 버전 요구 확인
  4. 필요 시 Podfile에서 특정 Pod 버전 핀(pin) (최후 수단)

예: 특정 Pod 버전을 고정해야 하는 상황(권장되진 않음)

target 'Runner' do
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))

  pod 'SomeSDK', '2.3.1'
end

버전 핀은 다른 플러그인과의 호환성을 깨기 쉬우므로, 가능하면 플러그인 업데이트/대체로 해결하는 것이 장기적으로 안전합니다.


8) CI에서 반복되는 pod install 실패를 줄이는 운영 팁

8.1 Pod 캐시/DerivedData 캐시 전략

  • CI에서 매번 pod repo update를 돌리면 변동성이 커집니다.
  • 대신 Podfile.lock 커밋 + pod install 고정을 기본으로 하고, 주기적으로만 업데이트합니다.

8.2 재현 가능한 명령 세트

CI 스크립트 예:

flutter --version
flutter pub get

cd ios
bundle install
bundle exec pod install
cd ..

flutter build ios --release --no-codesign
  • --no-codesign은 코드사인 이슈를 Pod 이슈와 분리하는 데 유용합니다.

권한/토큰 문제로 CI가 흔들릴 때는 원인별로 “어디서 403이 났는지”를 분리하는 습관이 중요합니다. 예: GitHub Actions GITHUB_TOKEN 403 권한 오류 해결


9) 최종 체크리스트(가장 빠른 해결 루트)

아래는 실제로 가장 많이 해결되는 “우선순위 높은” 루트입니다.

  1. flutter clean + Pods/Podfile.lock/.symlinks/DerivedData 제거
  2. pod deintegratepod repo updatepod install --verbose
  3. platform :ios, '13.0' 등 deployment target 상향
  4. Apple Silicon이면 EXCLUDED_ARCHS 또는 Pod 업데이트로 arm64 simulator 해결
  5. Ruby/CocoaPods 환경이 의심되면 Gemfile + bundle exec pod install로 고정
  6. Xcode 경로(xcode-select) 및 라이선스 확인
  7. 네트워크/CDN 문제면 --repo-update, trunk 재설정, 사내 프록시 정책 확인

결론

Flutter iOS 빌드에서 pod install 오류는 “한 번에 해결되는 마법의 명령”이 아니라, 환경(Ruby/CocoaPods/Xcode) + 설정(Podfile/Deployment Target) + 캐시(Pods/DerivedData) + 아키텍처(arm64) + 네트워크의 교차점에서 발생합니다.

위 순서대로 접근하면 대부분의 케이스에서 로그가 가리키는 단 하나의 병목으로 수렴하고, 불필요한 설정 변경 없이 안정적으로 복구할 수 있습니다. 프로젝트/CI 환경(로컬 Ruby 사용 여부, Bundler 도입 가능 여부, 최소 지원 iOS 버전)을 알려주면, 그 조건에 맞춰 “가장 안전한 Podfile 템플릿”과 “CI 캐시 전략”까지 함께 정리해 드릴 수 있습니다.