--- id: mobile-testflight-distribution title: Beta Distribution — TestFlight / Firebase / internal category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [mobile, beta, testflight, vibe-coding] tech_stack: { language: "process", applicable_to: ["Mobile"] } applied_in: [] aliases: [TestFlight, Firebase App Distribution, internal beta, external beta, Play Store internal track] --- # Beta Distribution > Production 전에 internal / external beta. **TestFlight (iOS), Play Store internal track + Firebase App Distribution (Android)**. 빠른 iterate + early feedback. ## 📖 핵심 개념 - Internal: 팀 (개발 / QA). - External: 선택 사용자 (beta tester). - Public beta: 모든 사용자 (open). - 매 build = beta → review. ## 💻 코드 패턴 ### TestFlight (iOS) ``` 1. Xcode → Product → Archive 2. Distribute → App Store Connect 3. 처리 (5-30 min) → TestFlight 4. Internal: 100 테스터 (instant) 5. External: 10000 테스터 (Apple review 1-2 days) ``` ### TestFlight 그룹 ``` Internal Group: - 매 team member (App Store Connect access) - 즉시 update - 90 days 만료 External Group: - 외부 사용자 (email + name) - Apple review (첫 build) - Public link 가능 ``` ### Internal 추가 ``` App Store Connect → Users and Access → Add user. Role: Developer / Marketing / etc. TestFlight access: ON. ``` ### External 추가 (email) ``` TestFlight tab → External Group → Add testers. - Email - 또는 public link (URL 하나) ``` ### Public link ``` - "Join beta" link - 무한 invite (limit 10000) - Limit 가능 (300 user 면 close) - Link 노출 → 누구나 = OK ``` ### Build expiration ``` TestFlight build = 90 days 만료. → 매 90 days 새 build 필요. 또는 expiry 전 새 build (더 좋음). ``` ### What to test ``` TestFlight 가 "What to Test" 표시. Markdown 가능. - 새 feature - Test 가 focus - Known issue - Feedback 요청 → Tester 가 무엇 test 모름. ``` ### Feedback (TestFlight) ``` - Screenshot 캡처 (iOS shake / volume) - 자동 attach + crash log - Email 으로 form - App Store Connect 에 표시 ``` → Email 보다 in-app feedback 친화. ### Firebase App Distribution (Android / iOS) ```bash # Setup firebase login firebase init appdistribution # Distribute firebase appdistribution:distribute app.apk \ --app 1:1234567890:android:abcdef \ --release-notes "Bug fixes" \ --groups "qa-team" ``` → iOS / Android 둘 다. Email + Firebase app. ### Play Store Internal Track ``` Google Play Console → Testing → Internal testing. - 100 user limit - 즉시 (review X) - Email 가입 ``` ### Play Store Closed / Open Testing ``` Closed: external (1k+ tester) Open: 모두 (Play Store 검색 가능, "Beta" 라벨) → Closed = staging. Open = public beta. ``` ### Play Store track ``` internal → closed → open → production 매 단계 = 별 release. Promote = 한 번에 다음 track 으로. ``` ### Fastlane (자동) ```ruby # Fastfile lane :beta do match(type: 'appstore') # cert build_app(scheme: 'MyApp') upload_to_testflight( skip_waiting_for_build_processing: true, changelog: "Beta update", distribute_external: true, groups: ['Beta Testers'], ) end ``` ```bash fastlane beta ``` → Local 또는 CI 가 한 번에 archive + upload. ### Android 자동 (Fastlane) ```ruby lane :playstore_internal do gradle(task: 'bundle', build_type: 'Release') upload_to_play_store( track: 'internal', aab: 'app/build/outputs/bundle/release/app-release.aab', ) end ``` ### CI (GitHub Actions) ```yaml - uses: actions/checkout@v4 - run: bundle install - run: bundle exec fastlane beta env: APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_API_KEY }} ``` ### Code signing (iOS) ``` match (Fastlane): - Cert + provisioning profile in private git repo - 모든 dev / CI 가 같은 cert → "code signing 안 됨" 문제 해결. ``` ```bash fastlane match appstore ``` ### Build number 자동 ```ruby # Fastfile lane :beta do increment_build_number(xcodeproj: 'MyApp.xcodeproj') build_app upload_to_testflight end ``` → 매 beta = 새 build number (자동). ### Versioning ``` 1.2.3 (build 45) Major.Minor.Patch + Build. Build = 매 upload. Patch = bug fix (1.2.3 → 1.2.4). Minor = 새 feature (1.2.3 → 1.3.0). Major = breaking (1.2.3 → 2.0.0). ``` ### Crash 추적 (beta) ```swift // Sentry / Crashlytics import Sentry SentrySDK.start { o in o.dsn = "..." o.environment = "beta" o.tracesSampleRate = 1.0 } ``` → Beta crash = critical signal. ### Phased rollout (production) ``` App Store: phased release (7 days). - Day 1: 1% - Day 2: 2% - Day 7: 100% Play Store: staged rollout (X%). ``` → Crash 발견 시 halt. ### Rollback (Play Store 만) ``` Play Console → 옛 release 다시 publish. → 새 install 가 옛 version. iOS = rollback 안 됨. 새 release 가 fix. ``` ### Beta 관리 ``` - Tester 그룹 (segments) - Feedback channel (Slack, Discord, email) - 매 release = 매 변경 list - Critical bug 가 아닌 = 다음 build ``` ### TestFlight invite UX ``` 1. User 가 email click 2. TestFlight app 다운로드 (없으면) 3. 가입 + redeem code 4. Beta app install → 3 step 가 pain. Public link 가 더 부드러움. ``` ### Demo build (다른 ID) ``` Build configuration: - Production: com.app - Beta: com.app.beta - Demo: com.app.demo → Tester 가 production 과 beta 동시 install. ``` ### LinkedIn / external review ``` 일부 tester: - Power user 가 좋음 - Diverse device (구 iPhone, 작은 storage) - 자주 보고 → 50명 < 5000 명 (engagement 가 중요). ``` ### Play Open testing → production ``` Open testing 의 user 가 production track 으로 자동 이동. - "Beta" 라벨 사라짐 - 같은 update 받음 → Soft launch. ``` ### 비용 ``` TestFlight: 무료 (Apple Developer $99/year 포함). Firebase App Distribution: 무료 (15k user 까지). Play Console: 무료 ($25 one-time). Fastlane: 무료. ``` ### Beta 함정 ``` - Build expiration 간과 (90 days) - "What to test" 안 적음 - Crash 무시 (production 에 가져감) - Tester 안 답 (ghost beta) - 너무 잦은 build (test 불가) ``` ## 🤔 의사결정 기준 | 단계 | 추천 | |---|---| | Internal QA | Internal group (TestFlight / Play internal) | | External beta (작음) | Closed group + email | | Public beta | Public link + Open testing | | Crash 추적 | Sentry / Crashlytics | | 자동화 | Fastlane + match | | Multi-platform | Firebase App Distribution | | Phased prod | Phased release / staged rollout | ## ❌ 안티패턴 - **Direct production**: bug 가 모든 사용자. - **Beta tester 0명**: feedback 없음. - **"What to test" 없음**: tester 가 뭐 할지 모름. - **Crash report 무시**: production 가져감. - **Manual upload**: 매번 30 min. - **Build version 가 같음**: TestFlight reject. - **Phased rollout 무시**: 1% 가 100% 가능. ## 🤖 LLM 활용 힌트 - TestFlight (iOS) + Play Internal Track (Android) = baseline. - Fastlane + match = 자동 + 안정. - Firebase App Distribution = cross-platform. - Crash + feedback channel 항상. ## 🔗 관련 문서 - [[Mobile_CI_CD_Fastlane]] - [[Mobile_Crash_Free_SLO]] - [[Mobile_App_Store_Optimization]]