Expo EAS Build 오류 해결: 로컬은 되는데 원격 빌드만 실패할 때

2026.05.10·수정 2026.07.20·약 17분

로컬은 되는데 EAS Build만 실패할 때의 진단 순서

Expo Go와 로컬 개발 서버는 정상인데 EAS Build의 Android 또는 iOS 원격 빌드만 실패하는 개발자를 위한 글입니다. 빌드 로그의 첫 유효 오류를 기준으로 업로드 아카이브, build profile과 환경변수, lockfile·도구 버전, native compile과 서명 단계를 차례로 분리합니다. 이 과정을 따르면 무작정 캐시를 지우거나 패키지를 재설치하지 않고 원격과 로컬의 입력 차이를 재현해 한 가지 원인을 수정하고, 같은 profile의 release 조건에서 성공 여부를 검증할 수 있습니다.

최신성 기준: 2026-07-19. 현재 Expo·EAS 공식 문서와 EAS CLI 명령 기준이며, SDK·Node·Xcode·Java 버전은 각 프로젝트의 lockfile과 선택한 EAS build image를 기준으로 고정해야 합니다.

실패 유형과 첫 유효 오류 분류하기

먼저 “빌드가 실패했다”와 “빌드는 성공했지만 설치 후 앱이 종료된다”를 나눕니다. 전자는 EAS Build 단계의 오류이고 후자는 runtime 오류입니다. Expo Go가 정상이라는 사실은 JavaScript 개발 런타임이 동작한다는 뜻일 뿐, 네이티브 모듈을 포함한 release 바이너리·서명·production 환경변수까지 검증했다는 뜻은 아닙니다.

EAS 대시보드에서 실패한 build의 플랫폼, profile, Git commit, Expo SDK, build image, 실패 단계와 로그를 저장합니다. 로그 맨 아래의 포괄적인 Build failed보다 위쪽에 처음 나타난 구체적인 파일명·패키지명·Gradle task·Xcode target 오류를 기준으로 분류합니다. Expo 공식 문제 해결 문서도 로그 확인 후 로컬 release 재현, 환경변수·도구 버전·업로드 파일 비교 순서를 권장합니다.

처음 실패한 단계 대표 단서 먼저 확인할 입력
프로젝트 아카이브·업로드 파일 없음, 모듈 경로 없음 .gitignore, .easignore, 경로 대소문자
dependency install immutable lockfile, package not found lockfile, package manager, private registry token
app config·Prebuild plugin, package name, bundle identifier 오류 profile 환경, 최종 app config, config plugin
native compile Gradle, Kotlin, CocoaPods, Xcode 오류 SDK 호환성, native module, build image
credentials·signing keystore, certificate, provisioning 오류 앱 식별자, 인증서·프로파일·권한
빌드 성공 후 runtime 즉시 종료, splash 화면 멈춤 production bundle, 환경변수, 네이티브 런타임 로그

재시도마다 build URL과 변경 한 가지를 함께 기록합니다. profile, 패키지, 캐시를 한 번에 모두 바꾸면 어떤 수정이 원인이었는지 확인할 수 없습니다. 로그를 공유할 때는 토큰, 서명 파일 내용, 인증서 비밀번호와 환경변수 값을 제거합니다.

release 조건으로 로컬 재현하기

개발 서버 대신 가장 싼 단계부터 release 조건을 올립니다. JavaScript bundle이나 production 환경 분기 문제는 npx expo export로 빠르게 드러날 수 있습니다. 그다음 네이티브 도구가 설치된 환경에서 Android release와 iOS Release 설정을 각각 실행합니다.

# JavaScript production bundle 확인
npx expo export

# Android release native compile
npx expo run:android --variant release

# iOS Release native compile: macOS와 Xcode 필요
npx expo run:ios --configuration Release

여기서도 실패하면 원격 서비스보다 프로젝트의 bundle·native 설정 문제일 가능성이 큽니다. 로컬 release가 성공한 뒤 EAS만 실패한다면 원격과 다른 세 가지 입력, 즉 도구 버전, 환경변수, 업로드 아카이브를 우선 비교합니다. 공식 문서가 제시하는 이 세 조건은 “로컬은 된다”를 검증 가능한 문장으로 바꾸는 기준입니다.

호스트에 필요한 native toolchain이 있다면 EAS 서버 단계와 더 가까운 로컬 빌드도 실행할 수 있습니다. 같은 플랫폼과 profile을 반드시 적습니다.

eas build --platform android --profile preview --local

# macOS에서 iOS를 재현할 때
eas build --platform ios --profile production --local

EAS 로컬 빌드 공식 문서에 따르면 --local은 원격 단계와 최대한 가까운 흐름을 실행하지만 호스트 운영체제와 설치된 toolchain까지 동일하게 만들어 주지는 않습니다. Windows의 Android 로컬 EAS Build는 first-class 지원이 아니며 WSL로 가능할 수 있고, iOS 로컬 compile은 macOS가 필요합니다. 로컬에서 재현하지 못했다고 곧바로 EAS 서비스 장애로 결론 내리면 안 됩니다.

업로드 아카이브와 대소문자 경로 확인하기

로컬 파일이 존재해도 EAS 서버에 업로드되지 않으면 원격에서는 없는 파일입니다. 기본적으로 EAS CLI는 .gitignore를 따르고, 프로젝트 루트에 .easignore가 있으면 그것을 우선합니다. .easignore 공식 문서대로 새 파일을 만들 때는 기존 .gitignore 규칙도 포함해야 하며, 필요한 비추적 파일은 마지막에 ! 예외 규칙으로 포함할 수 있습니다.

# 어떤 ignore 규칙이 파일을 제외하는지 확인
git check-ignore -v path/to/required-file.json

# EAS에 전달될 archive 단계를 별도 폴더로 검사
eas build:inspect -p android -s archive -o .eas-build-archive

현재 EAS CLI 공식 레퍼런스build:inspectarchive, pre-build, post-build 단계를 출력할 수 있습니다. 생성된 archive에서 로컬 config plugin, 인증에 필요한 설정 파일의 자리, workspace package와 native 디렉터리가 실제로 존재하는지 봅니다. 단, 비밀 파일을 저장소에 커밋하는 해결책은 피하고 EAS file environment variable이나 빌드 단계의 안전한 주입 방식을 사용합니다.

  • src/Api.ts를 import하면서 실제 파일이 src/api.ts라면 깨끗한 clone이나 원격 파일 시스템에서만 오류가 드러날 수 있습니다. import와 실제 이름의 대소문자를 정확히 맞춥니다.
  • androidios 디렉터리가 archive에 없으면 EAS Build는 Prebuild로 생성할 수 있습니다. 직접 관리하는 native 변경이 있다면 이 디렉터리가 의도치 않게 ignore되지 않았는지 확인합니다.
  • monorepo는 앱 루트뿐 아니라 workspace package, lockfile, 설정 파일이 archive에 포함되는지 확인합니다.

profile·환경변수·최종 app config 맞추기

eas build --profile previeweas.jsonbuild.preview를 사용합니다. --profile을 생략하면 production profile이 있으면 그것이 기본이므로, 실행 명령과 기대 환경이 다를 수 있습니다. 환경 선택을 암묵적인 distribution 규칙에 맡기지 않고 profile에 명시하면 Build와 Update의 값을 맞추기 쉽습니다.

{
  "build": {
    "preview": {
      "distribution": "internal",
      "environment": "preview"
    },
    "production": {
      "environment": "production"
    }
  }
}

로컬 .env.local은 보통 Git에서 제외되므로 원격 job에 자동으로 존재하지 않습니다. EAS 환경변수 공식 문서에 따라 EAS에 environment별 값을 만들고, 선택한 profile의 environment와 맞춥니다. 앱 클라이언트 코드에 들어가는 EXPO_PUBLIC_ 값은 번들 사용자가 읽을 수 있으므로 비밀키를 넣으면 안 됩니다.

# 값 자체를 로그에 붙이지 말고 이름·환경·visibility를 확인
eas env:list --environment preview

# 현재 로컬 환경에서 해석된 최종 공개 config 확인
npx expo config --type public

동적 app.config.ts가 앱 이름, android.package, ios.bundleIdentifier, config plugin 옵션을 환경변수로 만든다면 최종 config를 profile별로 비교합니다. Plain text와 Sensitive 변수는 EAS CLI의 build config 해석에도 사용할 수 있지만 Secret은 EAS 서버 밖에서 읽을 수 없습니다. 로컬 config 해석에도 반드시 필요한 값을 Secret로만 두면 undefined가 될 수 있으므로 목적과 visibility를 맞춥니다. Secret 값을 앱 번들에 넣는 것은 visibility와 상관없이 안전하지 않습니다.

lockfile·SDK·빌드 도구 버전 맞추기

EAS Build는 기본적으로 선택된 패키지 매니저의 immutable 방식, 예를 들어 npm의 npm ci나 Yarn의 --frozen-lockfile로 의존성을 설치합니다. 로컬에서 package.json만 바꾸고 lockfile을 갱신하지 않았거나 여러 종류의 lockfile이 섞여 있으면 원격 install 단계에서 먼저 실패할 수 있습니다. EAS dependency cache 공식 문서도 immutable lockfile 설치를 기본으로 설명합니다.

# 현재 Expo 프로젝트 상태와 config·dependency 호환성 점검
npx expo-doctor

# 변경 없이 호환 버전 차이만 확인
npx expo install --check

# 결과를 검토한 뒤 호환 버전으로 수정할 때만 실행
npx expo install --fix

원문의 npx expo doctor가 아니라 현재 공식 명령은 Expo 개발 도구 문서npx expo-doctor입니다. --fix는 실제 dependency와 lockfile을 바꿀 수 있으므로 먼저 --check 결과를 읽고 변경 diff와 native module 호환성을 확인합니다.

eas.json 공식 문서에서는 build profile에 Node와 패키지 매니저 버전, 플랫폼별 image를 지정할 수 있습니다. 로컬 성공 환경의 버전을 기록한 다음 필요한 값만 고정합니다. 무조건 latest로 올리거나 오래된 버전을 복사하는 대신 Expo SDK가 지원하는 build image와 lockfile 조합을 사용해야 합니다.

플랫폼별 native compile과 credentials 분리하기

범위 로그에서 볼 항목 확인할 설정
Android 실패한 Gradle task, Kotlin·Java compile, manifest merge android.package, keystore, config plugin, Gradle·JDK·SDK
iOS pod install, Xcode target, entitlement, code signing ios.bundleIdentifier, certificate, provisioning profile, capability
공통 dependency install, Metro bundle, Prebuild Expo SDK, lockfile, environment, 업로드 archive

한 플랫폼만 실패하면 공통 JavaScript 코드를 무작정 고치기보다 그 플랫폼의 첫 native 오류를 봅니다. Android 앱 식별자가 keystore나 Google 서비스 파일과 다르거나, iOS capability가 바뀌었는데 provisioning profile이 갱신되지 않은 경우가 대표적입니다. EAS가 credentials를 관리하는 프로젝트도 앱 식별자와 계정 권한이 맞아야 합니다.

CNG 프로젝트에서 native 디렉터리를 커밋하지 않으면 Prebuild가 app config와 config plugin으로 새 프로젝트를 생성합니다. 반대로 androidios를 직접 관리해 업로드하면 그 native 프로젝트가 빌드 입력입니다. 로컬에서 수동으로 바꾼 native 파일이 config plugin에 반영되지 않은 채 archive에서 제외되는 상황을 구분해야 합니다.

캐시와 로컬 빌드의 예외 확인하기

  • 캐시는 마지막 가설: lockfile, environment, archive가 같은데 오래된 artifact가 의심될 때만 eas build --platform android --profile preview --clear-cache로 한 번 비교합니다. --clear-cache는 현재 EAS CLI의 유효한 옵션이지만 누락 파일이나 잘못된 credentials를 고치지 않습니다.
  • 빌드 성공 후 crash는 별도 문제: npx expo export, production 환경변수, 기기 로그를 확인합니다. 빌드 단계의 cache clear만 반복하지 않습니다.
  • private package: 로컬 로그인 상태가 원격에 없을 수 있습니다. registry URL, lockfile, 원격 job에서 사용할 최소 권한 토큰의 이름을 확인하되 토큰 값을 로그로 출력하지 않습니다.
  • 서비스 장애: 같은 commit·profile이 과거에 성공했고 입력 차이가 없으며 여러 프로젝트에서 동일 단계가 실패할 때만 EAS 상태를 별도 가설로 둡니다.
  • 로컬 clean 재현: 기존 node_modules와 전역 설정에 기대지 않는 새 clone에서 immutable install과 release build를 통과해야 원격과 비교할 근거가 생깁니다.

수정 결과 검증과 결론

원인 하나를 수정한 뒤 실패했던 것과 같은 platform, profile, environment로 다시 빌드합니다. 단순히 status가 finished가 됐는지만 보지 말고 설치 가능한 artifact를 내려받아 시작, 핵심 화면, API 환경, 서명된 앱 식별자를 확인합니다.

검증 항목 통과 기준 남길 증거
업로드 입력 필요 파일과 workspace가 archive에 존재 build:inspect 단계와 파일 목록
환경·config 선택한 environment의 package·bundle identifier·공개 config 일치 값을 가린 config diff와 profile 이름
dependency immutable install과 Expo 호환성 검사 통과 lockfile hash, 패키지 매니저, expo-doctor 결과
native release 동일 플랫폼 release compile 통과 로컬 명령과 toolchain 버전
EAS 원격 build 같은 profile에서 artifact 생성 build URL·ID, commit, image, 실패 전후 단계
설치 후 실행 실기기·에뮬레이터에서 시작과 핵심 흐름 정상 앱 버전, 플랫폼, 테스트 결과

결론은 간단합니다. EAS Build만 실패하면 로컬 개발 서버가 아니라 원격에 전달된 입력을 비교해야 합니다. 첫 유효 오류를 단계별로 분류하고, release build로 재현한 뒤, archive·environment·lockfile·toolchain·credentials 중 다른 한 가지를 수정합니다. 같은 platform과 profile의 원격 빌드와 설치 테스트까지 통과하면 해결된 것입니다.

다음 학습 경로

이 글이 마음에 드세요?

RSS 피드를 구독하세요!