같은 Expo 앱인데 배포 방식은 달랐습니다: iOS와 Android 심사 제출 비교기
앞선 글에서는 이미 TestFlight에 올라간 iOS 빌드를 AI가 App Store 심사까지 제출한 과정을 다뤘습니다.
이번에는 같은 Expo 앱의 Android 버전을 Google Play 프로덕션 트랙으로 제출했습니다. 두 플랫폼 모두 EAS로 빌드했지만, 스토어에 제출하는 방식은 생각보다 많이 달랐습니다.
왜 이 차이를 알아야 할까요?
Expo를 사용하면 iOS와 Android의 빌드 명령은 거의 비슷해집니다. 그래서 스토어 제출 방식도 같을 것이라고 생각하기 쉽습니다.
하지만 EAS가 만든 파일이 스토어에 도착한 뒤부터는 플랫폼별 모델이 달라집니다. iOS는 App Store 버전에 TestFlight 빌드를 연결하고, Android는 업로드한 AAB를 출시 트랙에 배치합니다. 이 차이를 모르면 이미 등록된 Android 빌드를 다시 업로드하거나, EAS Submit이 App Review까지 끝내준다고 오해하기 쉽습니다.
먼저 한눈에 비교해 보겠습니다
| 구분 | iOS | Android |
|---|---|---|
| 빌드 산출물 | IPA | AAB |
| 테스트 배포 | TestFlight | Internal·Closed·Open testing |
| 버전 식별 | 버전 + 빌드 번호 | 버전 이름 + versionCode |
| 스토어 구조 | App Store 버전에 빌드 연결 | 빌드를 출시 트랙에 배치 |
| 제출 인증 | App Store Connect API 키 | Google 서비스 계정 |
| 출시 보류 | 수동 출시 설정 | 관리형 게시 |
| 자동화 API | App Store Connect API | Google Play Developer API |
Expo에서는 두 플랫폼 모두 EAS Build와 EAS Submit을 사용하지만, 그 이후의 스토어 모델이 다릅니다.
iOS에서는 빌드를 버전에 연결했습니다
iOS 작업을 시작했을 때 1.0.2 (7) 빌드는 이미 TestFlight에 올라가 있었습니다.
하지만 App Store용 1.0.2 버전은 아직 생성되지 않았습니다. 따라서 다음 작업이 필요했습니다.
TestFlight 빌드 확인
→ App Store 버전 1.0.2 생성
→ 빌드 7 연결
→ 언어별 업데이트 내용 입력
→ 심사 정보 확인
→ App Review 제출eas submit은 IPA를 App Store Connect에 업로드해 TestFlight에서 사용할 수 있게 해줍니다. 하지만 App Review 제출까지 자동으로 완료해 주는 것은 아닙니다.
Expo 공식 문서에서도 EAS Submit 이후 App Store Connect에서 빌드를 선택하고 App Review에 제출하는 단계를 별도로 안내합니다. Expo iOS 제출 문서
Android에서는 빌드를 트랙으로 승격했습니다
Android는 시작 상태가 조금 달랐습니다.
프로덕션: 1.0.1 (versionCode 4)
내부 테스트: 1.0.2 (versionCode 5)새 버전인 1.0.2가 이미 Google Play 내부 테스트 트랙에 배포되어 있었습니다. 따라서 새 AAB를 만들거나 다시 업로드할 필요가 없었습니다.
남은 작업은 내부 테스트의 versionCode 5를 프로덕션 트랙으로 승격하는 것이었습니다.
처음에는 EAS Submit을 다시 실행했습니다
프로젝트에는 다음과 비슷한 제출 설정이 있었습니다.
{
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "./secrets/google-play-service-account.json",
"track": "internal"
}
}
}
}가장 최근 Android 빌드를 제출하려면 다음 명령을 사용할 수 있습니다.
npx eas-cli@latest submit \
--platform android \
--profile production \
--latest하지만 이번에는 Google Play에서 이미 같은 versionCode가 제출되었다는 응답이 돌아왔습니다.
이 오류를 보고 무조건 versionCode를 올려서 다시 빌드하면 안 됩니다. 실제 원인은 빌드 실패가 아니라, 해당 AAB가 이미 Google Play에 등록되어 있었기 때문입니다.
Android의 versionCode는 새 업로드를 식별하는 고유 값입니다. 이미 업로드한 versionCode 5를 다시 업로드할 수는 없지만, 기존 빌드를 다른 트랙으로 승격할 수는 있습니다.
EAS Submit과 트랙 승격은 다른 작업입니다
EAS Submit은 새로운 AAB를 Google Play에 업로드할 때 편리합니다. Expo SDK 56 기준으로 프로덕션 프로필의 기본 Android 산출물은 Play Store에 제출할 수 있는 AAB입니다. Expo Android 제출 문서
# 프로덕션 AAB 생성
npx eas-cli@latest build \
--platform android \
--profile production
# Google Play 제출
npx eas-cli@latest submit \
--platform android \
--profile production \
--latest하지만 이미 내부 테스트에 존재하는 빌드를 프로덕션으로 옮기는 작업은 신규 업로드와 다릅니다.
이 경우에는 다음 방법을 사용할 수 있습니다.
- Google Play Console에서 직접 승격
- Google Play Developer API로 트랙 변경
- 새로운
versionCode로 다시 빌드한 뒤 프로덕션 트랙에 제출
코드가 달라지지 않았다면 기존 빌드를 승격하는 방식이 가장 자연스럽습니다.
Google Play Developer API는 Edit 단위로 동작합니다
Google Play Developer API의 배포 변경은 Edit이라는 트랜잭션 안에서 처리됩니다.
Edit 생성
→ 현재 트랙과 번들 조회
→ 프로덕션 트랙 수정
→ 변경사항 검증
→ Edit 커밋실제로 사용되는 API 흐름은 다음과 같습니다.
POST /applications/{packageName}/editsPUT /applications/{packageName}/edits/{editId}/tracks/productionPOST /applications/{packageName}/edits/{editId}:commit프로덕션 트랙에는 다음과 같은 출시 정보를 전달합니다.
{
"track": "production",
"releases": [
{
"name": "1.0.2",
"status": "completed",
"versionCodes": ["5"],
"releaseNotes": [
{
"language": "ko-KR",
"text": "라이트 모드에서 앱 시작 화면의 로고가 더 자연스럽게 보이도록 개선했습니다."
}
]
}
]
}여기서 status: completed는 Google 심사가 완료됐다는 뜻이 아닙니다. 단계적 출시가 아닌 전체 출시 대상으로 트랙을 구성했다는 의미입니다.
Google의 검토와 실제 게시 상태는 별도로 진행됩니다.
기존 심사를 취소하지 않도록 방어했습니다
Google Play API는 이미 다른 변경사항이 심사 중인 상태에서 새 Edit을 커밋하면 기존 심사를 취소하고 다시 제출할 수 있습니다.
이번 자동화에서는 다음 옵션을 사용했습니다.
changesInReviewBehavior=ERROR_IF_IN_REVIEW이미 심사 중인 변경사항이 있다면 기존 심사를 건드리지 않고 오류로 중단하도록 한 것입니다.
AI 배포 자동화에서는 성공적으로 제출하는 것만큼, 기존 심사와 출시 상태를 훼손하지 않는 방어 로직이 중요합니다. Google Play Edit 커밋 문서
실제 Android 제출 순서
이번 작업은 다음 순서로 진행했습니다.
- Git 작업 트리가 깨끗한지 확인했습니다.
- EAS의 최신 Android 프로덕션 빌드를 조회했습니다.
1.0.2 (versionCode 5)AAB가 정상적으로 완료되었는지 확인했습니다.- Google 서비스 계정의 필수 필드가 준비되어 있는지 검사했습니다.
- EAS Submit으로 제출을 시도했습니다.
- 동일한 versionCode가 이미 제출됐다는 응답을 확인했습니다.
- Google Play API로 현재 트랙과 업로드된 번들을 조회했습니다.
versionCode 5가 내부 테스트에 배포된 상태임을 확인했습니다.- 기존 프로덕션에는
versionCode 4가 배포되어 있음을 확인했습니다. versionCode 5를 프로덕션 트랙으로 승격했습니다.- 한국어와 영어 업데이트 내용을 등록했습니다.
- Edit을 커밋해 Google 심사 대상으로 제출했습니다.
- 프로덕션 트랙이
1.0.2 (5)로 변경되었는지 다시 검증했습니다. - 임시 배포 스크립트를 삭제하고 Git 상태를 확인했습니다.
이 과정에서 새로운 AAB를 만들거나 소스 코드를 수정하지 않았습니다.
인증 방식도 서로 다릅니다
iOS
App Store Connect API 자동화에는 일반적으로 다음 정보가 필요합니다.
- Key ID
- Issuer ID
.p8개인 키- Bundle Identifier
- App Store Connect 앱 ID
Android
Google Play 자동화에는 다음 정보가 필요합니다.
- Google Cloud 서비스 계정
- 서비스 계정 JSON 개인 키
- Google Play Console에서 부여한 앱 권한
- Android package name
서비스 계정 JSON에는 개인 키가 포함되어 있으므로 Git에 커밋하면 안 됩니다. EAS에 업로드하거나 CI 비밀 저장소에서 관리해야 합니다.
출시를 보류하는 방식도 다릅니다
iOS는 App Store 버전의 출시 방식을 수동 출시로 설정할 수 있습니다. 심사가 승인된 뒤 사람이 출시 버튼을 눌러 공개할 수 있습니다.
Android는 Google Play Console의 관리형 게시 기능을 사용합니다.
- 관리형 게시가 켜져 있으면 심사 승인 후에도 직접 게시할 때까지 대기합니다.
- 관리형 게시가 꺼져 있으면 심사 승인 후 자동으로 공개될 수 있습니다.
관리형 게시 여부는 배포 자동화 스크립트 밖에서 결정되는 계정 설정이므로, 프로덕션 제출 전에 반드시 확인해야 합니다. Google Play 관리형 게시 안내
두 플랫폼에서 공통으로 지켜야 할 원칙
플랫폼은 달라도 안전한 자동화 원칙은 같습니다.
- 먼저 현재 상태를 읽기 전용으로 조회합니다.
- 앱 식별자와 대상 버전을 고정해서 검증합니다.
- 새로운 빌드가 정말 필요한지 확인합니다.
- 동일한 버전을 중복 제출하지 않습니다.
- 기존 심사 중인 변경사항을 취소하지 않습니다.
- 개인정보와 정책 관련 답변을 AI가 추측하지 않도록 합니다.
- 인증정보를 출력하거나 저장소에 커밋하지 않습니다.
- 제출 후 스토어 상태를 다시 조회합니다.
- 실제 공개 시점은 별도의 승인 단계로 분리합니다.
듀오랩스가 보는 관점
Expo를 사용하면 iOS와 Android의 빌드 과정은 상당히 비슷해집니다.
소스 확인
→ EAS Build
→ EAS Submit하지만 스토어에 도착한 이후부터는 완전히 다른 모델을 이해해야 합니다.
iOS
TestFlight 빌드
→ App Store 버전 생성
→ 빌드 연결
→ App Review 제출
Android
내부 테스트 빌드
→ 프로덕션 트랙 승격
→ Edit 커밋
→ Google Play 검토이번 Android 배포에서 가장 중요한 판단은 오류가 발생했을 때 곧바로 새 빌드를 만들지 않은 것이었습니다.
같은 versionCode가 이미 제출됐다는 응답은 실패한 빌드가 아니라, 이미 Google Play에 도착한 빌드가 있다는 신호였습니다. 현재 트랙을 확인하고 기존 빌드를 프로덕션으로 승격하면서 불필요한 빌드와 버전 증가를 피할 수 있었습니다.
AI에게 배포를 맡긴다는 것은 명령어를 대신 입력하게 하는 것만을 의미하지 않습니다. 현재 상태를 읽고, 플랫폼별 배포 모델을 이해하고, 가장 안전한 다음 동작을 선택하도록 만드는 것이 진짜 자동화에 가깝습니다.
참고 문서
함께 읽기
- 코드는 그대로인데 앱 심사는 제출됐습니다: AI에게 Expo 배포를 맡겨본 기록코드를 한 줄도 수정하지 않았는데 TestFlight에 올라가 있던 앱이 App Review 제출 상태로 바뀌었습니다. 얼핏 보면 AI가 앱을 새로 빌드해서 배포한 것처럼 보이지만, 실제로는 이미 준비된 빌드와 스토어 정보를 확인한 뒤 App Store Connect의 심사 절차를 진행한 것입니다.
- Expo SDK 56 앱을 App Store와 Google Play에 배포하기Expo 앱을 배포한다고 하면 보통 eas build 명령 하나를 떠올립니다. 실제 운영 배포는 빌드, 앱 서명, 스토어 업로드, 베타 테스트, 심사, 공개 출시, OTA 업데이트가 서로 다른 단계입니다. 이 구분을 놓치면 CI가 성공했는데 스토어에는 앱이 없거나, JavaScript 수정이라고 생각해 OTA를 발행했는…
- 모바일 앱 스토어 배포 자동화를 설계할 때 볼 것들모바일 앱 배포는 빌드 파일을 만드는 것에서 끝나지 않습니다. 인증서, 서명 키, 스토어 메타데이터, 심사 정보, 버전 관리, 제출 자동화까지 함께 설계해야 반복 가능한 배포가 됩니다.
- Expo 앱은 만들었는데 출시가 안 됩니다: `eas build`보다 먼저 준비할 것들Expo를 사용하면 네이티브 빌드 과정이 크게 단순해집니다. 하지만 앱스토어 출시까지 명령어 한두 줄로 끝나는 것은 아닙니다. 실제 자동 배포를 만들려면 앱 등록, 서명 자격 증명, 스토어 API 권한, CI/CD 승인 절차가 먼저 준비되어야 합니다.
- Flutter 앱 배포, `flutter build` 한 줄이면 끝인 줄 알았습니다Flutter 앱의 빌드 명령은 간단합니다. Android는 flutter build appbundle, iOS는 flutter build ipa로 결과물을 만들 수 있습니다. 그러나 팀이 반복해서 사용할 수 있는 배포 체계를 만들려면 그 뒤에 있는 서명, 권한, 스토어 API, 심사와 출시 절차까지 함께 설계해야 합니…