Skip to content

Flutter CI/CD 전체 가이드 ​

Flutter 프로젝트를 위한 완전 자동화된 배포 파이프라인


목차 ​


개요 ​

projectops의 Flutter CI/CD 시스템은 마법사 도구와 GitHub Actions 워크플로우의 조합으로 구성됩니다.

핵심 특징:

  • 웹 UI 마법사로 복잡한 배포 설정 자동 생성
  • PR/이슈 댓글로 테스트 빌드 트리거
  • iOS TestFlight + Android Play Store + Firebase App Distribution 자동 배포

시스템 아키텍처 ​

전체 흐름 ​

┌─────────────────────────────────────────────────────────────────┐
│                        초기 설정 단계                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  🧙 TestFlight 마법사    🧙 Play Store 마법사   🧙 Firebase 마법사│
│  ├─ ExportOptions.plist  ├─ Fastfile           ├─ 배포 설정      │
│  ├─ Fastfile             ├─ 서명 설정          └─ 테스터 그룹    │
│  └─ Gemfile              └─ 서명 키 가이드                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                        개발 중 검증·테스트                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  develop 푸시/PR → PROJECT-FLUTTER-CI.yaml (분석 + 빌드 검증)    │
│                                                                  │
│  PR/이슈에 빌드 명령어 댓글 (build app/apk build/ios build)     │
│                     ↓                                            │
│  PROJECT-FLUTTER-PROJECTOPS-APP-BUILD-TRIGGER.yaml (트리거)      │
│                     ↓  repository_dispatch                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ PROJECT-FLUTTER-ANDROID-TEST-APK.yaml  → APK 아티팩트   │    │
│  │ PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml → TestFlight   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                     ↓                                            │
│  빌드 결과 댓글 자동 작성                                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                        본 배포 단계                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  main 브랜치 push                                                │
│           ↓                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml       → TestFlight  │    │
│  │ PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml → Play Store│    │
│  │ PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml  → Firebase  │    │
│  │ PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml → 자체 서버│    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

본 배포 워크플로우 4종은 모두 main push 트리거입니다. 프로젝트에 필요 없는 워크플로우는 삭제하거나 비활성화하세요.

마법사-워크플로우 관계 ​

.github/util/flutter/testflight-wizard/
    → 생성: ExportOptions.plist, Fastfile, Gemfile
    → 사용 워크플로우:
        - PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml (본 배포)
        - PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml (테스트)

.github/util/flutter/playstore-wizard/
    → 생성: Fastfile, build.gradle.kts 서명 설정
    → 사용 워크플로우:
        - PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml (본 배포)
        - PROJECT-FLUTTER-ANDROID-TEST-APK.yaml (테스트)

.github/util/flutter/firebase-wizard/
    → 생성: Firebase App Distribution 배포 설정
    → 사용 워크플로우:
        - PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml (본 배포)
        - PROJECT-FLUTTER-ANDROID-TEST-APK.yaml (Firebase 업로드 옵션)

마법사 도구 ​

마법사용도상세 가이드
TestFlight 마법사iOS 배포 설정 자동 생성FLUTTER-TESTFLIGHT-WIZARD.md
Play Store 마법사Android Play Store 배포 설정 자동 생성FLUTTER-PLAYSTORE-WIZARD.md
Firebase 마법사Firebase App Distribution 배포 설정 자동 생성FLUTTER-FIREBASE-WIZARD.md

워크플로우 목록 ​

CI (코드 검증) ​

워크플로우용도트리거
PROJECT-FLUTTER-CI.yaml코드 분석 + 빌드 검증develop push / develop 대상 PR

본 배포 워크플로우 ​

워크플로우용도트리거
PROJECT-FLUTTER-IOS-TESTFLIGHT.yamliOS TestFlight 배포main push
PROJECT-FLUTTER-IOS-ASC-STATUS.yamlApp Store Connect 버전 상태와 최근 빌드 번호 조회 (조회 전용, 리눅스 러너)수동 실행
PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yamlAndroid Play Store 내부 테스트 배포main push
PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yamlFirebase App Distribution 배포main push
PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml자체 서버(SMB) APK 배포main push

셀프호스트 APK 서명: 기본은 디버그 키이며 경고를 남긴다. 저장소 변수 ANDROID_SELFHOSTED_RELEASE_SIGNING=true 를 등록해야만 RELEASE_KEYSTORE_BASE64, RELEASE_KEYSTORE_PASSWORD, RELEASE_KEY_ALIAS, RELEASE_KEY_PASSWORD 로 릴리스 서명하며, 이때 시크릿이 하나라도 비면 빌드가 실패한다. 서명 키가 바뀌면 이미 설치된 앱 위에 업데이트 설치가 되지 않을 수 있으니 주의한다. 업로드 하위 경로는 워크플로 env.SMB_PATH_ANDROID(기본 빈 값)로 지정한다.

배포 범위는 어디까지 가는가 (DEPLOY_MODE) ​

한 값이 두 플랫폼에서 서로 다른 지점까지 간다. 같은 이름이라 헷갈리기 쉬워 표로 둔다.

DEPLOY_MODEiOSAndroid
store_only (기본)TestFlight 업로드까지내부 테스트 트랙까지
store_prepareApp Store 버전에 빌드를 붙임 (제출 안 함)프로덕션 draft 승급 (콘솔에서 "출시 시작"을 사람이 누름)
store_submitApp Store 심사 제출프로덕션 심사 자동 등록

구 별칭(testflight_only·appstore_prepare·appstore_submit)도 계속 받는다.

기본값은 store_only 다 — 워크플로도, Fastfile 내부도 같다. 예전에는 Android Fastfile 만 store_submit 이라, 워크플로를 거치지 않고 lane 을 직접 부르면 프로덕션 심사에 올라갔다 (#618 에서 교정).

스토어 '새로운 기능' 문구 (STORE_WHATS_NEW_OVERRIDE) ​

store_prepare/store_submit 때 스토어 '새로운 기능(What's New)' 칸(iOS '이 버전의 새로운 기능', Android '출시 노트')에 들어갈 문구다. 사용자뿐 아니라 심사자도 본다. 워크플로 파일 env에 둔다 (iOS·Android 같은 이름 — 두 곳을 함께 바꾼다).

값동작
"" (템플릿 기본값)CHANGELOG 해당 버전 항목을 넣는다 — 이전과 같다
문구버전과 상관없이 매번 그 문구를 넣는다
  • 공백만 있는 값은 빈 값으로 본다. 빌드 로그에 출처(CHANGELOG / STORE_WHATS_NEW_OVERRIDE 덮어쓰기)가 남는다.
  • 수동 실행 입력 whats_new_override에 값을 주면 그 실행만 우선한다.
  • iOS TestFlight 'What to Test'(내부 테스터용)는 이 값과 상관없이 항상 CHANGELOG를 쓴다.
  • Android 출시 노트는 내부 테스트 업로드 때 정해져 승급에 그대로 따라가므로 모든 트랙에 같이 적용된다.
  • 레포 변수가 아니라 env인 이유: 템플릿은 레포 변수를 미리 넣어 줄 수 없고, 거의 바꾸지 않는 값이라 코드에서 보이고 git 이력이 남는 편이 낫다. (배포 모드는 급할 때 바로 꺼야 해서 레포 변수 그대로다.)

iOS 심사 메모(Notes) — ios/fastlane/review_notes.txt가 있으면 매번 그 내용을 넣고, 없거나 비어 있으면 App Store Connect 기존값을 그대로 둔다. (예전에는 빈 파일로 '초기화'하려 했지만 빈 값은 전송되지 않아 기존값이 남았다 — 실측.) 심사용 공용 계정 없이 로그인 안내를 Notes에만 두는 앱은 이 파일로 코드와 함께 관리한다.

Android 트랙 — 내부 테스트만으로는 프로덕션에 못 간다 ⚠️ ​

DEPLOY_MODE 는 프로덕션 단계만 정한다. 중간 트랙은 독립 스위치가 맡는다.

스위치 (레포 변수 / 수동 실행 입력)하는 일기본
ANDROID_PROMOTE_TO_CLOSED_TESTING비공개 테스트 트랙에도 올린다false
ANDROID_PROMOTE_TO_OPEN_TESTING공개 테스트 트랙에도 올린다false
ANDROID_CLOSED_TESTING_TRACK비공개 트랙 이름alpha
ANDROID_OPEN_TESTING_TRACK공개 트랙 이름beta
트랙구글 검토반영프로덕션 액세스 조건에 포함
internal (내부)없음수 분❌
alpha (비공개)있음수십 분 ~ 수일✅
beta (공개)있음〃✅
production있음〃—

2023-11-13 이후에 만든 개인 개발자 계정은 테스터 12명 이상이 14일 이상 참여한 비공개 테스트를 마쳐야 프로덕션 액세스를 받는다. 내부 테스트는 검토가 없어 편하지만 그 조건에 잡히지 않는다 — 내부만 돌리면 조건이 영영 차지 않는다. (그 이전에 만든 계정과 조직 계정은 해당하지 않는다.)

트랙 이름은 콘솔에서 바꿀 수 있으므로 값으로 둔다. 박아두면 임의 이름을 쓰는 저장소에서 조용히 안 올라간다.

테스트 빌드 워크플로우 ​

워크플로우용도트리거
PROJECT-FLUTTER-PROJECTOPS-APP-BUILD-TRIGGER.yaml빌드 트리거 감지@projectops build app / apk build / ios build 댓글
PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yamliOS 테스트 빌드repository_dispatch (build-ios-app)
PROJECT-FLUTTER-ANDROID-TEST-APK.yamlAndroid APK 테스트 빌드repository_dispatch (build-android-app)

상세 가이드: FLUTTER-TEST-BUILD-TRIGGER.md

iOS 빌드 번호와 버전 (#643) ​

항목동작
빌드 번호2024-01-01 UTC부터 지난 초 (약 8,700만), 아카이브 직전에 결정. ASC 최근 번호와 Apple 거부 메시지가 요구한 번호는 하한으로만 쓰임. 규칙은 build_number.py 한 곳
테스트 빌드 버전version.yml 버전. 출시되어 닫힌 버전이면 다음 패치로 빌드하고 진행 댓글에 이유를 표시
릴리스 버전version.yml 버전. 닫힌 버전이면 빌드 전에 실패 (version.yml 버전을 올린 뒤 다시 실행)
업로드 거부너무 낮은 번호는 요구값 이상의 새 번호로 최대 5회 재시도 (Flutter 빌드는 다시 하지 않음). 닫힌 버전 거부는 테스트만 다음 패치로 재시도. 번호 중복은 그 번호가 이미 올라가 있다는 뜻이라 재업로드 없이 실패하며 다시 실행하면 새 번호를 씀
실패 사유테스트 빌드는 실패 댓글에 사유 한 줄, 릴리스는 실행 요약과 ::error::로 표시

version_code는 iOS와 테스트 빌드 번호에 쓰이지 않습니다 (VERSION-CONTROL.md). 자세한 규칙은 FLUTTER-TEST-BUILD-TRIGGER.md를 참고하세요.


빠른 시작 ​

1단계: 마법사로 설정 파일 생성 ​

bash
# iOS TestFlight 설정
open .github/util/flutter/testflight-wizard/testflight-wizard.html

# Android Play Store 설정
open .github/util/flutter/playstore-wizard/playstore-wizard.html

# Firebase App Distribution 설정
open .github/util/flutter/firebase-wizard/firebase-wizard.html

2단계: GitHub Secrets 설정 ​

아래 GitHub Secrets 전체 목록을 참고해 등록합니다.

⚠️ Secret 이름은 워크플로우가 참조하는 이름과 정확히 일치해야 합니다. 이름이 하나라도 다르면 인증서/키스토어 복원 단계에서 빌드가 실패합니다.

3단계: 워크플로우 설치 ​

bash
# npx 마법사로 Flutter 워크플로우 설치
npx projectops --mode workflows --type flutter

4단계: 테스트 빌드 실행 ​

PR 또는 이슈에 댓글 작성:

@projectops build app    # Android + iOS 모두 빌드
@projectops apk build    # Android만 빌드
@projectops ios build    # iOS만 빌드

GitHub Secrets 전체 목록 ​

iOS (TestFlight — 본 배포 / 테스트 빌드 공통) ​

Secret설명
APPLE_CERTIFICATE_BASE64Apple Distribution 인증서 .p12 (base64 인코딩)
APPLE_CERTIFICATE_PASSWORD.p12 인증서 비밀번호
APPLE_PROVISIONING_PROFILE_BASE64.mobileprovision 파일 (base64 인코딩)
IOS_PROVISIONING_PROFILE_NAME프로비저닝 프로파일 이름
APP_STORE_CONNECT_API_KEY_IDApp Store Connect API Key ID (10자리)
APP_STORE_CONNECT_ISSUER_IDIssuer ID (UUID 형식)
APP_STORE_CONNECT_API_KEY_BASE64AuthKey_XXXXXX.p8 파일 (base64 인코딩)
IOS_BUNDLE_ID (선택)번들 ID. Secret 대신 저장소 변수(vars)로도 지정 가능
ENV_FILE (선택).env 파일 내용
SECRETS_XCCONFIG (선택)ios/Flutter/Secrets.xcconfig 내용

App Store Connect API 키 역할: App Manager 이상을 권장합니다. 준비 단계가 이 키로 앱의 버전 목록과 최근 빌드 번호를 읽는데, Developer 역할은 읽기 권한이 확인되지 않았습니다. 조회가 안 되면 경고만 남기고 version.yml 버전으로 진행하며, 업로드가 닫힌 버전으로 거부될 때의 자동 복구는 동일하게 동작합니다. 새로 필요한 Secret은 없습니다.

Android — Play Store 배포 ​

Secret설명
RELEASE_KEYSTORE_BASE64서명용 keystore .jks (base64 인코딩)
RELEASE_KEYSTORE_PASSWORDkeystore 비밀번호
RELEASE_KEY_ALIASkey alias
RELEASE_KEY_PASSWORDkey 비밀번호
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64Play Console 서비스 계정 JSON (base64 인코딩)
GOOGLE_SERVICES_JSONFirebase google-services.json 내용
ENV_FILE 또는 ENV (선택).env 파일 내용 (ENV_FILE 우선)

Android — Firebase App Distribution 배포 ​

Play Store와 동일한 RELEASE_* 서명 Secret을 쓰고, 업로드 자격만 다릅니다.

Secret설명
RELEASE_KEYSTORE_BASE64 / _PASSWORD서명용 keystore 및 비밀번호
RELEASE_KEY_ALIAS / RELEASE_KEY_PASSWORDkey alias 및 비밀번호
FIREBASE_SERVICE_ACCOUNT_JSON_BASE64Firebase 서비스 계정 JSON (base64 인코딩)
GOOGLE_SERVICES_JSON (선택)Firebase google-services.json 내용
ENV_FILE 또는 ENV (선택).env 파일 내용

Android — 자체 서버(SMB) 배포 ​

Secret설명
RELEASE_KEYSTORE_BASE64 / _PASSWORD서명용 keystore 및 비밀번호
RELEASE_KEY_ALIAS / RELEASE_KEY_PASSWORDkey alias 및 비밀번호
SERVER_HOST / SERVER_USER / SERVER_PASSWORDSMB 접속 정보
GOOGLE_SERVICES_JSON (선택)Firebase google-services.json 내용
ENV_FILE 또는 ENV (선택).env 파일 내용

각 워크플로우 파일 상단 🔑 필수 GitHub Secrets 주석이 항상 최신 기준입니다. 이 표와 어긋나면 워크플로우 주석을 신뢰하세요.


파일 위치 요약 ​

.github/
├── util/flutter/
│   ├── testflight-wizard/           # iOS 마법사
│   │   ├── testflight-wizard.html
│   │   ├── testflight-wizard.js
│   │   ├── testflight-wizard.py
│   │   └── templates/
│   │       ├── ExportOptions.plist
│   │       ├── Fastfile.ios.template
│   │       └── Gemfile
│   │
│   ├── playstore-wizard/            # Android Play Store 마법사
│   │   ├── playstore-wizard.html
│   │   ├── playstore-wizard.js
│   │   ├── playstore-wizard.py
│   │   └── templates/
│   │       ├── Fastfile.playstore.template
│   │       └── build.gradle.kts.signing.template
│   │
│   └── firebase-wizard/             # Firebase App Distribution 마법사
│       ├── firebase-wizard.html
│       ├── firebase-wizard.js
│       └── firebase-wizard.py
│
└── workflows/project-types/flutter/
    ├── PROJECT-FLUTTER-CI.yaml
    ├── PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml
    ├── PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml
    ├── PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml
    ├── PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml
    ├── PROJECT-FLUTTER-PROJECTOPS-APP-BUILD-TRIGGER.yaml
    ├── PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml
    └── PROJECT-FLUTTER-ANDROID-TEST-APK.yaml

관련 문서 ​