Android Play Store 마법사 상세 가이드
Flutter Android 앱을 Play Store에 배포하기 위한 설정 마법사
목차
개요
Play Store 마법사는 웹 UI를 통해 Android 배포에 필요한 설정 파일들을 자동으로 생성해주는 도구입니다.
위치: .github/util/flutter/playstore-wizard/
버전: 1.5.0 (정확한 값은 마법사 헤더의 버전 배지 또는 version.json 참조)
호환성:
- Flutter >= 3.0.0
- Android SDK >= 33
- Fastlane >= 2.220.0
사전 요구사항
마법사 실행 전에 다음 항목들을 준비해야 합니다:
1. Google Play Console 등록
- play.google.com/console 에서 개발자 계정 등록
- 일회성 등록비 $25
⚠️ 개인 계정이면 비공개 테스트가 사실상 필수다.2023-11-13 이후에 만든 개인 개발자 계정은 테스터 12명 이상이 14일 이상 참여한 비공개 테스트를 마쳐야 프로덕션 액세스를 받는다. 이 템플릿의 기본 배포는 내부 테스트 트랙까지인데, 내부 테스트는 그 조건에 잡히지 않는다.
따라서 개인 계정이라면 배포 설정에서 비공개 테스트 승급을 켜 둔다. 안 켜면 배포는 계속 나가는데 프로덕션 액세스 조건이 영영 차지 않고, 비공개 트랙만 손으로 올리게 되어 두 트랙의 버전이 벌어진다.
켜는 방법과 트랙별 성격은 FLUTTER-CICD-OVERVIEW.md 의 "Android 트랙" 절. (그 이전에 만든 계정·조직 계정은 해당하지 않는다.)
2. 서명 키 생성
- Release 서명용 Keystore 파일 필요
- 키 분실 시 앱 업데이트 불가능하므로 안전하게 보관
# Keystore 생성 명령어
keytool -genkey -v -keystore release-key.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias release-key3. Google Play 서비스 계정 생성
- Google Cloud Console에서 서비스 계정 생성
- Play Console과 연동
- JSON 키 파일 다운로드
마법사 사용법
실행 방법
# 브라우저에서 마법사 열기
open .github/util/flutter/playstore-wizard/playstore-wizard.html
# 또는 설정 스크립트 실행
# macOS/Linux
python3 .github/util/flutter/playstore-wizard/playstore-wizard.py setup
# Windows
python .github\util\flutter\playstore-wizard\playstore-wizard.py setup마법사 진행 단계
| 단계 | 내용 | 입력 정보 |
|---|---|---|
| 1 | 시작 | 마법사 소개 |
| 2 | 키스토어 | 서명 키 생성 가이드 |
| 3 | 서비스 계정 | Google Play 서비스 계정 생성 가이드 |
| 4 | 앱 정보 | Application ID 입력 |
| 5 | Fastlane | 생성된 설정 파일 다운로드 |
| 6 | 완료 | 설정 완료 확인 |
입력해야 할 정보
# Application ID (패키지명)
Application ID: "com.company.appname"
# Keystore 정보
Keystore Path: "android/app/release-key.jks"
Keystore Password: "your-keystore-password"
Key Alias: "release-key"
Key Password: "your-key-password"로컬 스크립트 직접 실행 (CLI)
python3 .github/util/flutter/playstore-wizard/playstore-wizard.py setup \
--project-path . \
--application-id com.example.app \
--key-alias my-release-key \
--store-password MyPass123 --key-password MyPass123 \
--validity-days 99999 \
--cert-cn "My Name" --cert-o "My Company" --cert-l "Seoul" --cert-c "KR"| 옵션 | 설명 |
|---|---|
--project-path | Flutter 프로젝트 루트 경로 |
--application-id | Android Application ID |
--key-alias | Keystore alias |
--store-password / --key-password | Keystore / Key 비밀번호 |
--validity-days | 인증서 유효기간(일) |
--cert-cn / --cert-o / --cert-l / --cert-c | 인증서 정보 |
공통 옵션 (마법사 3종 동일)
| 옵션 | 설명 |
|---|---|
--dry-run | 무엇을 바꿀지만 출력. keystore도 실제로 만들지 않음 |
--no-backup | 기존 파일 백업(.bak)을 만들지 않음 |
--non-interactive | 확인 프롬프트 없이 진행 (CI용) |
⚠️ 구 위치인자 형식(10개)도 계속 동작하지만, 비밀번호가 4·5번째 자리라 순서를 한 칸만 틀려도 잘못된 값이 저장됩니다. 명명 플래그를 권장합니다.
생성되는 파일
1. Fastfile.playstore
Play Store 배포 자동화 스크립트입니다.
위치: android/fastlane/Fastfile.playstore
제공하는 lane:
build_aab- AAB 빌드deploy_internal- Internal Testing 업로드 +DEPLOY_MODE에 따른 승급 (비공개/공개 테스트, 프로덕션)promote_internal_to_closed_testing/promote_internal_to_open_testing- 중간 트랙 승급promote_internal_to_production- 프로덕션 승급 (PRODUCTION_ROLLOUT, 기본 1.0 = 전체 출시)validate- 서비스 계정 검증
배포 모드를 코드로 관리: .github/config/store-deploy.json(예시는 store-deploy.json.example)에 android.deploy_mode와 android.production_rollout을 적으면 git에 남고 리뷰할 수 있다. 결정 순서는 수동 실행 입력 > 설정 파일 > 레포 변수(ANDROID_DEPLOY_MODE) > store_only 이고, 파일이 없으면 기존 레포 변수 방식 그대로 동작한다. 이 파일은 템플릿이 싣지 않으므로 업데이트 때 덮어써지지 않으며, 잘못된 값은 조용히 무시하지 않고 배포 전에 실패한다. iOS(TestFlight)도 같은 파일의 ios.deploy_mode를 같은 방식으로 읽는다.
파일은 직접 쓰지 않아도 된다. playstore-wizard.py setup 끝에 --deploy-mode store_prepare --production-rollout 0.1 을 붙이면 마법사가 android 섹션에 기록한다 (선택 플래그, 안 주면 아무것도 쓰지 않는다). 이미 있는 ios 섹션과 사용자가 적은 키는 지우지 않고, 값이 잘못되면 키스토어를 만들기 전에 실패하며, --dry-run 에서는 파일을 만들지 않는다.
프로덕션 출시 비율: 레포 변수 ANDROID_PRODUCTION_ROLLOUT(0 초과 1.0 이하, 기본 1.0)로 정한다. 1.0은 심사 통과 즉시 전체 사용자에게 나가며 되돌릴 수 없다. 1.0 미만(예: 0.1)은 그 비율만 단계적으로 내보내고, 비율을 올리는 일은 자동화되지 않아 Play Console 프로덕션 > 출시 관리에서 사람이 올린다. 문제가 생기면 같은 화면에서 출시를 중단한다.
최초 1회는 수동 출시: 한 번도 프로덕션에 출시된 적 없는 앱은 API로 프로덕션 승급이 되지 않는다. 콘솔에서 최초 1회 출시해야 하며, 이미 비공개 테스트에 올라간 빌드는 프로덕션 > 새 출시 > "라이브러리에서 추가"로 새로 빌드하지 않고 붙일 수 있다.
Fastfile을 직접 고쳤다면: 워크플로가 넘기는 값(DEPLOY_MODE, PROMOTE_*, PRODUCTION_ROLLOUT)을 Fastfile이 읽지 않으면 조용히 무시된다. 워크플로가 이를 감지해 CI 로그에 경고를 남기지만 막지는 않는다. 마법사를 다시 실행하면 이 파일은 템플릿으로 다시 쓰이므로 직접 고친 내용은 사라진다. 실행 전에 diff로 확인한다.
필요한 환경변수:
AAB_PATH- AAB 파일 경로GOOGLE_PLAY_JSON_KEY- 서비스 계정 JSON 파일 경로
2. build.gradle.kts 서명 설정
앱 서명 설정 코드입니다.
위치: android/app/build.gradle.kts (수동 추가 필요)
서명 값은 환경변수가 아니라 android/key.properties 파일에서 읽습니다. CI에서는 워크플로우가 Secret으로 이 파일과 keystore를 복원합니다.
// key.properties 로드
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String? ?: ""
keyPassword = keystoreProperties["keyPassword"] as String? ?: ""
storeFile = keystoreProperties["storeFile"]?.let { rootProject.file(it) }
storePassword = keystoreProperties["storePassword"] as String? ?: ""
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}android/key.properties 형식 (로컬 빌드용, .gitignore에 반드시 추가):
storeFile=keystore/key.jks
storePassword=스토어_비밀번호
keyAlias=키_별칭
keyPassword=키_비밀번호전체 템플릿은 .github/util/flutter/playstore-wizard/templates/build.gradle.kts.signing.template을 참고하세요.
GitHub Secrets 설정
워크플로우 실행을 위해 다음 Secrets를 설정해야 합니다:
| Secret 이름 | 설명 | 값 형식 |
|---|---|---|
RELEASE_KEYSTORE_BASE64 | Release Keystore (.jks) | Base64 인코딩 |
RELEASE_KEYSTORE_PASSWORD | Keystore 비밀번호 | 문자열 |
RELEASE_KEY_ALIAS | 키 별칭 | 문자열 |
RELEASE_KEY_PASSWORD | 키 비밀번호 | 문자열 |
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 | Play Console 서비스 계정 JSON | Base64 인코딩 |
GOOGLE_SERVICES_JSON | Firebase google-services.json 내용 | JSON 문자열 |
ENV_FILE 또는 ENV (선택) | .env 파일 내용 (ENV_FILE 우선) | 문자열 |
⚠️ Secret 이름은 워크플로우가 참조하는 이름과 정확히 일치해야 합니다. 서명 키 계열은
RELEASE_접두사이며, Play Console 서비스 계정은 JSON 원문이 아니라 base64 인코딩 값(..._BASE64)입니다.
Base64 인코딩 방법
# Keystore 인코딩
base64 -i release-key.jks | pbcopy # macOS
base64 release-key.jks # Linux
# Play Console 서비스 계정 JSON 인코딩
base64 -i service-account.json | pbcopy # macOS
base64 service-account.json # Linux서비스 계정 JSON 설정
아래 형태의 서비스 계정 JSON 파일을 base64로 인코딩한 값을 GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64에 설정합니다:
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "your-service-account@your-project.iam.gserviceaccount.com",
"client_id": "...",
...
}연관 워크플로우
본 배포
- 파일:
PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml - 트리거: main 브랜치 push
- 용도: Play Store Internal Testing 배포
테스트 빌드
- 파일:
PROJECT-FLUTTER-ANDROID-TEST-APK.yaml - 트리거:
@projectops build app또는@projectops apk build댓글 (repository_dispatch) - 용도: PR/이슈에서 테스트 APK 빌드
자체 서버(SMB) 배포
- 파일:
PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml - 트리거: main 브랜치 push
- 용도: 자체 서버(Synology NAS, 일반 NAS, Linux 등)의 SMB 공유에 APK 배포
트러블슈팅
서명 관련 오류
❌ Error: Keystore was tampered with, or password was incorrect해결:
RELEASE_KEYSTORE_PASSWORD/RELEASE_KEY_PASSWORD가 정확한지 확인RELEASE_KEYSTORE_BASE64의 Base64 인코딩이 올바르게 되었는지 확인- Keystore 파일이 손상되지 않았는지 확인
서비스 계정 오류
❌ Error: The caller does not have permission해결:
- 서비스 계정에 Play Console 권한이 부여되었는지 확인
- Play Console → Users and permissions → 서비스 계정 추가
- "Release apps to testing tracks" 권한 필요
AAB 파일 오류
❌ Error: AAB file not found해결:
- Flutter 빌드가 성공했는지 확인
flutter build appbundle --release실행- AAB 파일 경로가 맞는지 확인:
build/app/outputs/bundle/release/app-release.aab
Application ID 불일치
❌ Error: Package name does not match해결:
- Fastfile의
package_name과android/app/build.gradle의applicationId일치 확인 - Play Console에 등록된 패키지명과 일치하는지 확인
파일 구조
.github/util/flutter/
├── _shared/ # 마법사 3종 공통 자산
│ ├── wizard.css # 공통 컴포넌트 스타일
│ ├── wizard-common.js # 공통 유틸 (이스케이프·클립보드·상태 저장 등)
│ ├── check-consistency.py # 3종 정합성 검증
│ └── test_wizard_cli.py # CLI 계약 · dry-run 안전성 테스트
└── playstore-wizard/
├── playstore-wizard.html # 마법사 웹 UI
├── playstore-wizard.js # 마법사 로직
├── playstore-wizard.py # 설정 스크립트 (setup / apply / detect-app-id, 전 OS 공용)
├── version.json # 버전 정보
├── version-sync.sh # version.json → HTML 동기화
└── templates/
├── Fastfile.playstore.template # Fastfile 템플릿
└── build.gradle.kts.signing.template # 서명 설정 템플릿
_shared/는 3종이 함께 쓰는 정본입니다. 마법사를 수정했다면check-consistency.py와test_wizard_cli.py를 통과시킨 뒤 커밋하세요. 상세:_shared/README.md