🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

빌드와 배포 자동화 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2026. 2. 3. · 3 Views

빌드와 배포 자동화 완벽 가이드

Flutter 앱 개발에서 GitHub Actions를 활용한 CI/CD 파이프라인 구축부터 앱 스토어 자동 배포까지, 초급 개발자도 쉽게 따라할 수 있는 빌드 자동화의 모든 것을 다룹니다.


목차

  1. GitHub_Actions_설정
  2. 자동_빌드_파이프라인
  3. 앱_스토어_자동_배포
  4. 버전_관리_전략
  5. 베타_테스트_배포
  6. 릴리즈_노트_자동화

1. GitHub Actions 설정

김개발 씨는 오늘도 야근입니다. 코드 한 줄 수정했을 뿐인데, 빌드하고 테스트하고 배포하는 데 2시간이 걸렸습니다.

"이걸 매번 수동으로 해야 한다고?" 옆자리 박시니어 씨가 웃으며 말합니다. "GitHub Actions 써봤어요?"

GitHub Actions는 코드 저장소에서 직접 자동화 워크플로우를 실행할 수 있는 CI/CD 플랫폼입니다. 마치 충실한 비서가 정해진 시간에 정해진 일을 알아서 처리해주는 것과 같습니다.

코드를 푸시하면 자동으로 빌드, 테스트, 배포까지 일사천리로 진행됩니다.

다음 코드를 살펴봅시다.

# .github/workflows/flutter_ci.yml
name: Flutter CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # 저장소 코드를 가져옵니다
      - uses: actions/checkout@v4

      # Flutter 환경을 설정합니다
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          channel: 'stable'

      # 의존성을 설치합니다
      - run: flutter pub get

      # 코드 분석을 실행합니다
      - run: flutter analyze

      # 테스트를 실행합니다
      - run: flutter test

김개발 씨는 입사 6개월 차 주니어 개발자입니다. Flutter로 모바일 앱을 개발하고 있는데, 매번 코드를 수정할 때마다 같은 작업을 반복해야 했습니다.

빌드하고, 테스트 돌리고, 문제가 있으면 다시 수정하고. 이 과정이 하루에도 수십 번 반복되니 정작 중요한 개발에 집중할 시간이 없었습니다.

어느 날 팀 회의에서 박시니어 씨가 GitHub Actions에 대해 설명해주었습니다. "코드를 푸시하면 알아서 빌드하고 테스트하는 거예요.

우리는 결과만 확인하면 됩니다." 그렇다면 GitHub Actions란 정확히 무엇일까요? 쉽게 비유하자면, GitHub Actions는 마치 24시간 일하는 로봇 비서와 같습니다.

여러분이 "코드가 올라오면 이런 일을 해줘"라고 한 번만 지시해두면, 그 뒤로는 알아서 척척 처리합니다. 새벽 3시에 코드를 푸시해도, 주말에 긴급 수정을 해도, 로봇 비서는 불평 없이 일합니다.

GitHub Actions가 없던 시절에는 어땠을까요? 개발자들은 Jenkins 같은 별도의 CI 서버를 구축해야 했습니다.

서버를 관리하고, 플러그인을 설치하고, 네트워크를 설정하는 것만으로도 며칠이 걸렸습니다. 더 큰 문제는 이 서버가 다운되면 전체 팀의 업무가 마비된다는 것이었습니다.

소규모 스타트업에서는 엄두도 못 낼 일이었습니다. 바로 이런 문제를 해결하기 위해 GitHub Actions가 등장했습니다.

GitHub Actions를 사용하면 별도의 서버 없이 GitHub 안에서 모든 자동화가 가능합니다. YAML 파일 하나만 작성하면 됩니다.

무엇보다 무료 사용량이 넉넉해서 개인 프로젝트나 소규모 팀에서도 부담 없이 사용할 수 있습니다. 위의 코드를 하나씩 살펴보겠습니다.

먼저 on 섹션을 보면 언제 이 워크플로우가 실행될지 정의합니다. main이나 develop 브랜치에 푸시하거나, main 브랜치로 PR을 올리면 자동으로 실행됩니다.

다음으로 jobs 섹션에서는 실제로 수행할 작업들을 정의합니다. runs-on은 어떤 환경에서 실행할지 지정하는데, ubuntu-latest를 사용하면 리눅스 서버에서 실행됩니다.

steps는 순서대로 실행될 단계들입니다. checkout으로 코드를 가져오고, flutter-action으로 Flutter 환경을 구성합니다.

그 다음 의존성 설치, 코드 분석, 테스트를 차례로 실행합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 5명의 개발자가 같은 프로젝트를 진행한다고 가정해봅시다. 각자 다른 기능을 개발하고 PR을 올릴 때마다 자동으로 테스트가 실행됩니다.

테스트가 실패하면 병합이 차단되어 버그가 있는 코드가 main 브랜치에 들어가는 것을 방지할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 워크플로우 파일 경로를 잘못 설정하는 것입니다. 반드시 .github/workflows/ 폴더 안에 YAML 파일을 생성해야 합니다.

또한 들여쓰기가 잘못되면 전체 워크플로우가 실행되지 않으니 주의하세요. 다시 김개발 씨의 이야기로 돌아가 봅시다.

GitHub Actions를 설정한 뒤, 김개발 씨의 업무 방식이 완전히 바뀌었습니다. 코드를 푸시하면 자동으로 테스트 결과가 슬랙으로 날아옵니다.

"이제 진짜 개발에만 집중할 수 있겠어요!"

실전 팁

💡 - 워크플로우 파일은 반드시 .github/workflows/ 폴더에 생성하세요

  • flutter-action의 버전을 명시하면 일관된 빌드 환경을 유지할 수 있습니다
  • 캐시를 활용하면 빌드 시간을 크게 단축할 수 있습니다

2. 자동 빌드 파이프라인

"빌드가 또 실패했어요!" 김개발 씨가 한숨을 쉽니다. 로컬에서는 잘 되던 코드가 CI 서버에서는 왜 실패하는 걸까요?

박시니어 씨가 다가와 말합니다. "빌드 파이프라인을 제대로 구성하지 않아서 그래요.

캐싱이랑 병렬 처리 설정해봐요."

자동 빌드 파이프라인은 소스 코드를 실행 가능한 앱으로 변환하는 전체 과정을 자동화한 것입니다. 마치 공장의 조립 라인처럼, 원재료(코드)가 들어가면 완제품(앱)이 나오는 시스템입니다.

캐싱과 병렬 처리를 활용하면 빌드 시간을 획기적으로 단축할 수 있습니다.

다음 코드를 살펴봅시다.

# .github/workflows/build_pipeline.yml
name: Build Pipeline

on:
  push:
    branches: [ main ]

jobs:
  build-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      # 의존성 캐싱으로 빌드 속도 향상
      - uses: actions/cache@v4
        with:
          path: |
            ~/.pub-cache
            .dart_tool
          key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}

      - run: flutter pub get

      # Android APK 빌드
      - run: flutter build apk --release

      # 빌드 결과물 업로드
      - uses: actions/upload-artifact@v4
        with:
          name: android-release
          path: build/app/outputs/flutter-apk/app-release.apk

  build-ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      # iOS 빌드 (서명 없이)
      - run: flutter build ios --release --no-codesign

김개발 씨는 빌드 시간 때문에 고민이 많았습니다. 코드 한 줄 수정하고 빌드 결과를 확인하는 데 15분이나 걸렸습니다.

하루에 빌드를 10번만 해도 2시간 반이 날아갑니다. "이 시간을 줄일 수 없을까요?" 박시니어 씨가 노트북 화면을 보여주며 설명했습니다.

"빌드 파이프라인을 최적화하면 절반 이하로 줄일 수 있어요." 그렇다면 자동 빌드 파이프라인이란 정확히 무엇일까요? 쉽게 비유하자면, 빌드 파이프라인은 마치 자동차 공장의 조립 라인과 같습니다.

철판이 들어가면 로봇들이 순서대로 용접하고, 도색하고, 조립해서 완성된 자동차가 나옵니다. 코드도 마찬가지입니다.

소스 코드가 들어가면 컴파일하고, 패키징하고, 서명해서 앱이 나오는 것입니다. 빌드 파이프라인을 최적화하지 않으면 어떤 문제가 생길까요?

매번 처음부터 모든 의존성을 다운로드받아야 합니다. Flutter 프로젝트의 경우 수백 개의 패키지를 매번 새로 받으면 그것만으로도 5분이 소요됩니다.

더 큰 문제는 Android와 iOS 빌드를 순차적으로 실행하면 시간이 두 배로 늘어난다는 것입니다. 바로 이런 문제를 해결하기 위해 캐싱병렬 처리가 필요합니다.

캐싱을 사용하면 한 번 다운로드한 의존성을 저장해두고 재사용합니다. actions/cache를 활용하면 pub-cache와 dart_tool 폴더를 캐싱할 수 있습니다.

pubspec.lock 파일의 해시값을 키로 사용하면, 의존성이 변경될 때만 새로 다운로드합니다. 위의 코드에서 핵심적인 부분을 살펴보겠습니다.

build-androidbuild-ios 두 개의 job이 정의되어 있습니다. 이 두 job은 서로 의존성이 없기 때문에 병렬로 실행됩니다.

Android 빌드는 ubuntu-latest에서, iOS 빌드는 macos-latest에서 동시에 진행됩니다. 이렇게 하면 전체 빌드 시간이 둘 중 긴 쪽에 맞춰집니다.

upload-artifact 액션을 사용하면 빌드 결과물을 GitHub에 저장할 수 있습니다. 나중에 다운로드하거나 다음 단계에서 사용할 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 대형 프로젝트에서는 빌드를 더 세분화합니다.

단위 테스트, 통합 테스트, 빌드, 배포를 각각 별도의 job으로 분리하고, 필요한 것만 실행합니다. PR에서는 테스트만 실행하고, main 브랜치에 병합될 때만 실제 빌드를 수행하는 식입니다.

하지만 주의할 점도 있습니다. iOS 빌드는 반드시 macOS 환경에서 실행해야 합니다.

GitHub Actions에서 macOS runner는 Linux보다 비용이 10배 높습니다. 무료 플랜에서는 사용량이 제한되니 꼭 필요한 경우에만 iOS 빌드를 실행하도록 조건을 설정하세요.

김개발 씨는 캐싱과 병렬 처리를 적용한 뒤, 빌드 시간이 15분에서 6분으로 줄었습니다. "시간이 반 이상 줄었어요!

이제 퇴근이 빨라지겠네요."

실전 팁

💡 - 캐시 키에 pubspec.lock 해시를 포함하면 의존성 변경 시에만 캐시가 갱신됩니다

  • iOS 빌드는 비용이 높으니 main 브랜치에서만 실행하도록 조건을 추가하세요
  • 빌드 결과물은 artifact로 저장해두면 나중에 다운로드할 수 있습니다

3. 앱 스토어 자동 배포

"배포할 때마다 스크린샷 캡처하고, 설명 작성하고, 파일 업로드하고... 이거 2시간은 걸리잖아요." 김개발 씨가 투덜댑니다.

박시니어 씨가 말합니다. "Fastlane 써봤어요?

버튼 하나로 앱 스토어에 배포할 수 있어요."

앱 스토어 자동 배포는 빌드된 앱을 Google Play Store와 Apple App Store에 자동으로 업로드하는 프로세스입니다. 마치 택배 기사가 물건을 정해진 주소로 배달해주는 것처럼, Fastlane이 앱을 정해진 스토어로 배달해줍니다.

한 번 설정해두면 코드 푸시만으로 전 세계 사용자에게 앱이 배포됩니다.

다음 코드를 살펴봅시다.

# .github/workflows/deploy.yml
name: Deploy to Stores

on:
  push:
    tags:
      - 'v*'  # v1.0.0 형식의 태그가 푸시되면 실행

jobs:
  deploy-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      # 서명 키 설정
      - name: Decode Keystore
        run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore.jks

      # App Bundle 빌드
      - run: flutter build appbundle --release
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

      # Google Play에 업로드
      - uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_JSON }}
          packageName: com.example.myapp
          releaseFiles: build/app/outputs/bundle/release/app-release.aab
          track: production

김개발 씨는 앱 출시일을 앞두고 긴장하고 있었습니다. App Store Connect에 로그인해서 빌드를 업로드하고, 스크린샷을 등록하고, 심사 요청을 보내야 합니다.

지난번에는 이 과정에서 실수로 잘못된 버전을 올려서 심사가 거절당한 적도 있었습니다. "사람이 하는 일에는 항상 실수가 따라요." 박시니어 씨가 말했습니다.

"그래서 자동화가 필요한 거예요." 그렇다면 앱 스토어 자동 배포란 정확히 무엇일까요? 쉽게 비유하자면, 앱 스토어 자동 배포는 마치 자동 택배 시스템과 같습니다.

여러분이 물건을 포장해서 컨베이어 벨트에 올려놓으면, 시스템이 알아서 분류하고, 주소 라벨을 붙이고, 배송 트럭에 실어줍니다. 개발자는 코드에 버전 태그만 달아주면, 나머지는 시스템이 알아서 처리합니다.

수동 배포의 문제점은 무엇이었을까요? 첫째, 시간이 오래 걸립니다.

App Store Connect와 Google Play Console에 각각 로그인해서 빌드를 업로드하고, 메타데이터를 입력하고, 심사를 요청해야 합니다. 둘째, 실수하기 쉽습니다.

피곤한 상태에서 배포하다가 프로덕션 대신 개발 버전을 올리는 사고가 종종 발생합니다. 바로 이런 문제를 해결하기 위해 자동 배포 파이프라인을 구축합니다.

위의 코드에서 핵심 부분을 살펴보겠습니다. on.push.tags를 보면 'v*' 패턴의 태그가 푸시될 때만 이 워크플로우가 실행됩니다.

즉, git tag v1.0.0을 만들고 푸시하면 자동으로 배포가 시작됩니다. 이렇게 하면 실수로 배포가 실행되는 것을 방지할 수 있습니다.

secrets는 GitHub의 비밀 저장소입니다. 서명 키나 API 토큰 같은 민감한 정보를 안전하게 저장할 수 있습니다.

코드에 직접 비밀번호를 적는 것은 매우 위험하지만, secrets를 사용하면 보안을 유지하면서 자동화를 구현할 수 있습니다. upload-google-play 액션은 빌드된 App Bundle을 Google Play Store에 업로드합니다.

track을 production으로 설정하면 바로 출시되고, internal이나 beta로 설정하면 테스트 트랙에 배포됩니다. 실제 현업에서는 어떻게 활용할까요?

대부분의 팀에서는 바로 프로덕션에 배포하지 않습니다. 먼저 internal 트랙에 배포해서 내부 테스트를 진행하고, 문제가 없으면 beta 트랙으로 이동해서 베타 테스터들의 피드백을 받습니다.

최종적으로 production 트랙에 배포하는 단계적 롤아웃 전략을 사용합니다. 하지만 주의할 점도 있습니다.

서명 키를 분실하면 앱 업데이트가 불가능해집니다. Google Play에서는 같은 서명 키로 서명된 앱만 업데이트로 인정하기 때문입니다.

반드시 서명 키를 안전한 곳에 백업해두세요. GitHub Secrets에 저장하더라도 원본은 별도로 보관해야 합니다.

김개발 씨는 자동 배포를 설정한 뒤, git tag v1.2.0 && git push --tags 한 줄로 배포를 완료합니다. "커피 마시고 오면 배포가 끝나 있어요!"

실전 팁

💡 - 서명 키는 base64로 인코딩해서 GitHub Secrets에 저장하세요

  • 처음에는 internal 트랙으로 배포해서 테스트한 뒤 production으로 변경하세요
  • Google Play App Signing을 활성화하면 키 관리가 더 안전해집니다

4. 버전 관리 전략

"이번에 배포한 버전이 뭐였죠?" 김개발 씨가 묻자, 팀원들이 각자 다른 대답을 합니다. "1.2.0이요.", "아니, 1.2.1 아니었어요?", "저는 1.3.0으로 알고 있는데..." 박시니어 씨가 한숨을 쉬며 말합니다.

"버전 관리 전략을 정해야겠어요."

버전 관리 전략은 앱의 버전 번호를 체계적으로 관리하는 규칙입니다. 마치 책의 개정판을 구분하는 것처럼, 버전 번호를 보면 어떤 변화가 있었는지 알 수 있어야 합니다.

Semantic Versioning을 따르면 MAJOR.MINOR.PATCH 형식으로 변화의 크기를 명확히 전달할 수 있습니다.

다음 코드를 살펴봅시다.

# pubspec.yaml에서 버전 관리
name: my_flutter_app
version: 1.2.3+45  # 버전: 1.2.3, 빌드 번호: 45

# 버전 자동 증가 스크립트 (scripts/bump_version.dart)
import 'dart:io';

void main(List<String> args) {
  final pubspec = File('pubspec.yaml');
  var content = pubspec.readAsStringSync();

  // 현재 버전 파싱
  final versionRegex = RegExp(r'version: (\d+)\.(\d+)\.(\d+)\+(\d+)');
  final match = versionRegex.firstMatch(content)!;

  var major = int.parse(match.group(1)!);
  var minor = int.parse(match.group(2)!);
  var patch = int.parse(match.group(3)!);
  var build = int.parse(match.group(4)!);

  // 인자에 따라 버전 증가
  switch (args.first) {
    case 'major': major++; minor = 0; patch = 0; break;
    case 'minor': minor++; patch = 0; break;
    case 'patch': patch++; break;
  }
  build++;  // 빌드 번호는 항상 증가

  // 새 버전 저장
  final newVersion = '$major.$minor.$patch+$build';
  content = content.replaceFirst(versionRegex, 'version: $newVersion');
  pubspec.writeAsStringSync(content);
  print('Updated to version: $newVersion');
}

김개발 씨네 팀에는 버전 관리에 대한 규칙이 없었습니다. 어떤 개발자는 기능이 추가될 때마다 버전을 올렸고, 어떤 개발자는 배포할 때만 올렸습니다.

결과적으로 버전 번호가 뒤죽박죽이 되어버렸습니다. 사용자가 "버전 1.5.0에서 버그가 있어요"라고 신고했을 때, 어떤 코드가 해당 버전에 포함되어 있는지 찾는 것조차 어려웠습니다.

그렇다면 버전 관리 전략이란 정확히 무엇일까요? 쉽게 비유하자면, 버전 관리는 마치 책의 개정판 번호와 같습니다.

"3판 2쇄"라고 적혀 있으면, 큰 내용 변경이 3번 있었고 그 뒤로 2번의 소규모 수정이 있었음을 알 수 있습니다. 소프트웨어의 버전 번호도 마찬가지입니다.

가장 널리 사용되는 방식은 Semantic Versioning입니다. MAJOR.MINOR.PATCH 형식으로, 각 숫자는 특정한 의미를 가집니다.

MAJOR는 하위 호환성이 깨지는 변경이 있을 때 올립니다. API가 완전히 바뀌어서 기존 사용자가 코드를 수정해야 하는 경우입니다.

MINOR는 하위 호환성을 유지하면서 새 기능이 추가될 때 올립니다. PATCH는 버그 수정 같은 작은 변경에 사용합니다.

Flutter에서는 추가로 빌드 번호를 관리합니다. pubspec.yaml에서 1.2.3+45라고 적으면, 1.2.3은 사용자에게 보이는 버전이고 45는 스토어에서 사용하는 빌드 번호입니다.

빌드 번호는 항상 이전보다 커야 합니다. 위의 코드는 버전을 자동으로 증가시키는 스크립트입니다.

dart run scripts/bump_version.dart patch를 실행하면 패치 버전이 1 올라갑니다. minor나 major를 인자로 주면 해당 버전이 올라가고, 하위 버전은 0으로 리셋됩니다.

빌드 번호는 어떤 경우든 항상 증가합니다. 실제 현업에서는 어떻게 활용할까요?

많은 팀에서 git tag와 버전을 연동합니다. 버전을 올릴 때 같은 이름의 태그를 만들고, 이 태그가 푸시되면 자동으로 배포가 시작됩니다.

이렇게 하면 특정 버전의 코드를 쉽게 찾을 수 있고, 릴리즈 히스토리가 명확해집니다. 하지만 주의할 점도 있습니다.

한 번 스토어에 배포된 버전 번호는 다시 사용할 수 없습니다. 실수로 같은 빌드 번호로 다시 배포하려고 하면 거부됩니다.

따라서 CI/CD에서 자동으로 빌드 번호를 증가시키는 것이 안전합니다. GitHub Actions의 run_number 변수를 활용하면 매번 자동으로 증가하는 빌드 번호를 얻을 수 있습니다.

김개발 씨네 팀은 Semantic Versioning을 도입한 뒤, 버전 혼란이 사라졌습니다. "이제 버전 번호만 보면 무슨 변화가 있었는지 바로 알 수 있어요!"

실전 팁

💡 - MAJOR 버전은 정말 큰 변화가 있을 때만 올리세요 (너무 자주 올리면 의미가 없습니다)

  • 빌드 번호는 CI의 run_number를 활용하면 자동으로 관리됩니다
  • 버전 변경 시 반드시 git tag를 함께 생성하세요

5. 베타 테스트 배포

"새 기능 테스트해보고 싶은데 아직 출시 전이라..." 김개발 씨가 고민하자, 박시니어 씨가 말합니다. "베타 테스트 채널 만들어서 일부 사용자에게 먼저 배포해보세요.

출시 전에 버그를 잡을 수 있어요."

베타 테스트 배포는 정식 출시 전에 제한된 사용자 그룹에게 앱을 배포하여 피드백을 받는 과정입니다. 마치 영화 시사회처럼, 일반 공개 전에 소수의 관객에게 먼저 보여주고 반응을 확인하는 것입니다.

Google Play의 내부 테스트, 비공개 테스트, 공개 테스트 트랙을 활용하면 단계적으로 테스트 범위를 넓힐 수 있습니다.

다음 코드를 살펴봅시다.

# .github/workflows/beta_deploy.yml
name: Beta Deploy

on:
  push:
    branches: [ develop ]

jobs:
  deploy-beta:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      - name: Decode Keystore
        run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore.jks

      # 베타 버전 빌드 (버전에 베타 표시)
      - name: Build Beta APK
        run: |
          BUILD_NUMBER=${{ github.run_number }}
          flutter build apk --release --build-number=$BUILD_NUMBER
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

      # Firebase App Distribution으로 베타 배포
      - uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          appId: ${{ secrets.FIREBASE_APP_ID }}
          serviceCredentialsFileContent: ${{ secrets.FIREBASE_CREDENTIALS }}
          file: build/app/outputs/flutter-apk/app-release.apk
          groups: beta-testers
          releaseNotes: |
            Build: ${{ github.run_number }}
            Commit: ${{ github.sha }}
            Changes: ${{ github.event.head_commit.message }}

김개발 씨는 새로운 결제 기능을 개발했습니다. 로컬에서는 완벽하게 동작하는 것 같았지만, 실제 사용자 환경에서는 어떤 문제가 생길지 알 수 없었습니다.

바로 프로덕션에 배포했다가 결제 오류가 발생하면 큰일이었습니다. "그래서 베타 테스트가 필요한 거예요." 박시니어 씨가 설명했습니다.

"소수의 테스터에게 먼저 배포하고, 문제가 없으면 전체에 공개하는 거죠." 그렇다면 베타 테스트 배포란 정확히 무엇일까요? 쉽게 비유하자면, 베타 테스트는 마치 신메뉴 시식회와 같습니다.

레스토랑에서 새 메뉴를 정식으로 출시하기 전에, 단골손님 몇 명을 초대해서 맛보게 합니다. "간이 좀 센 것 같아요", "양이 적어요" 같은 피드백을 받아 메뉴를 개선한 뒤 정식 출시하는 것입니다.

베타 테스트 없이 바로 출시하면 어떤 문제가 생길까요? 첫째, 예상치 못한 버그가 전체 사용자에게 영향을 줍니다.

개발자가 테스트한 기기와 사용자의 기기는 다릅니다. 둘째, 사용자 경험 문제를 놓칠 수 있습니다.

개발자에게는 당연한 기능이 일반 사용자에게는 어려울 수 있습니다. 베타 테스터들의 피드백으로 이런 문제를 미리 발견할 수 있습니다.

위의 코드는 develop 브랜치에 푸시하면 자동으로 베타 배포가 실행되는 워크플로우입니다. 핵심은 Firebase App Distribution입니다.

Google Play 베타 트랙을 사용할 수도 있지만, Firebase는 설정이 더 간단하고 테스터 관리가 쉽습니다. groups 파라미터에 beta-testers를 지정하면, 미리 등록해둔 베타 테스터 그룹에게만 앱이 배포됩니다.

releaseNotes에는 이번 빌드의 변경 사항을 적습니다. 커밋 메시지를 자동으로 포함시키면 테스터가 무엇이 바뀌었는지 알 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 단계적으로 테스트 범위를 넓히는 전략을 사용합니다.

먼저 내부 테스터(회사 직원)에게 배포합니다. 치명적인 버그가 없으면 비공개 베타(충성 사용자)로 범위를 넓힙니다.

마지막으로 공개 베타(누구나 신청 가능)를 거쳐 정식 출시합니다. 이렇게 하면 위험을 최소화하면서 충분한 테스트를 할 수 있습니다.

하지만 주의할 점도 있습니다. 베타 테스터도 결국 실제 사용자입니다.

너무 불안정한 버전을 배포하면 테스터들이 떠나버립니다. 최소한 기본 기능은 동작하는 상태에서 베타 배포를 진행하세요.

또한 테스터들의 피드백에 적극적으로 응답해야 합니다. 피드백을 보내도 아무 반응이 없으면 테스터들의 참여율이 떨어집니다.

김개발 씨는 베타 테스트 덕분에 결제 기능의 버그를 미리 발견했습니다. "베타 테스터 한 분이 특정 카드에서 오류가 난다고 알려줬어요.

프로덕션 배포 전에 고쳐서 정말 다행이에요!"

실전 팁

💡 - Firebase App Distribution은 설정이 간단하고 테스터 관리가 쉽습니다

  • 테스터에게 변경 사항을 명확히 알려주면 더 유용한 피드백을 받을 수 있습니다
  • 베타 버전에는 크래시 리포팅 도구(Firebase Crashlytics)를 반드시 포함하세요

6. 릴리즈 노트 자동화

"지난주에 뭘 배포했더라?" 김개발 씨가 릴리즈 노트를 작성하려는데 기억이 나지 않습니다. 커밋 로그를 하나씩 뒤지다가 한숨을 쉽니다.

박시니어 씨가 말합니다. "커밋 메시지를 규칙에 맞게 작성하면 릴리즈 노트가 자동으로 만들어져요."

릴리즈 노트 자동화는 커밋 히스토리를 분석하여 변경 사항을 자동으로 문서화하는 프로세스입니다. 마치 일기장을 정리해서 연간 리뷰를 만드는 것처럼, 매일 작성한 커밋 메시지가 모여 릴리즈 노트가 됩니다.

Conventional Commits 규칙을 따르면 기능 추가, 버그 수정, 주요 변경 사항을 자동으로 분류할 수 있습니다.

다음 코드를 살펴봅시다.

# .github/workflows/release_notes.yml
name: Generate Release Notes

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 전체 히스토리 가져오기

      # Conventional Commits 기반 릴리즈 노트 생성
      - name: Generate Changelog
        id: changelog
        uses: TriPSs/conventional-changelog-action@v5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          output-file: false
          skip-commit: true
          skip-version-file: true

      # GitHub Release 생성
      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          body: ${{ steps.changelog.outputs.clean_changelog }}
          files: |
            build/app/outputs/flutter-apk/app-release.apk
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# 커밋 메시지 예시 (Conventional Commits)
# feat: 다크 모드 지원 추가
# fix: 로그인 버튼이 간헐적으로 동작하지 않는 문제 수정
# docs: README에 설치 방법 추가
# BREAKING CHANGE: 최소 지원 버전이 Android 6.0으로 변경

김개발 씨는 매번 릴리즈 노트 작성이 고역이었습니다. 지난 배포 이후 무엇이 바뀌었는지 커밋 로그를 하나씩 확인해야 했습니다.

"기능 추가 3건, 버그 수정 5건..." 정리하다 보면 어느새 한 시간이 훌쩍 지나있었습니다. "커밋 메시지를 처음부터 잘 작성하면 그럴 필요가 없어요." 박시니어 씨가 말했습니다.

그렇다면 릴리즈 노트 자동화란 정확히 무엇일까요? 쉽게 비유하자면, 릴리즈 노트 자동화는 마치 가계부 앱과 같습니다.

매일 지출을 입력하면 월말에 자동으로 지출 리포트가 생성됩니다. "식비 30%, 교통비 20%..." 같은 분류가 자동으로 됩니다.

커밋 메시지도 마찬가지입니다. 규칙에 맞게 작성하면 자동으로 분류되어 릴리즈 노트가 됩니다.

이를 위해 Conventional Commits라는 규칙을 사용합니다. 모든 커밋 메시지를 정해진 형식으로 작성합니다.

**feat:**는 새로운 기능, **fix:**는 버그 수정, **docs:**는 문서 변경, **style:**은 코드 스타일 변경, **refactor:**는 리팩토링을 의미합니다. 이 규칙을 따르면 도구가 자동으로 커밋을 분류할 수 있습니다.

위의 코드에서 핵심 부분을 살펴보겠습니다. conventional-changelog-action은 Conventional Commits 형식의 커밋 메시지를 분석하여 체인지로그를 생성합니다.

feat: 커밋은 "Features" 섹션에, fix: 커밋은 "Bug Fixes" 섹션에 자동으로 배치됩니다. BREAKING CHANGE가 포함된 커밋은 "Breaking Changes" 섹션에 따로 표시됩니다.

이 정보는 사용자에게 매우 중요합니다. 업데이트 후 호환성 문제가 생길 수 있음을 미리 알려주기 때문입니다.

softprops/action-gh-release는 GitHub Releases 페이지에 새 릴리즈를 생성합니다. 생성된 체인지로그를 본문으로 사용하고, 빌드된 APK 파일을 첨부합니다.

실제 현업에서는 어떻게 활용할까요? 많은 오픈소스 프로젝트에서 이 방식을 사용합니다.

기여자가 PR을 올릴 때 커밋 메시지 규칙을 따르도록 요구하고, 메인테이너는 자동 생성된 릴리즈 노트를 검토만 하면 됩니다. 커밋 메시지 규칙을 강제하기 위해 commitlint 같은 도구를 사용하기도 합니다.

하지만 주의할 점도 있습니다. 자동화가 좋은 릴리즈 노트를 보장하지는 않습니다.

커밋 메시지의 품질이 중요합니다. "fix: 버그 수정"이라는 커밋 메시지로는 무슨 버그가 수정되었는지 알 수 없습니다.

"fix: 결제 완료 후 주문 내역이 표시되지 않는 문제 수정"처럼 구체적으로 작성해야 합니다. 김개발 씨는 Conventional Commits를 도입한 뒤, 릴리즈 노트 작성 시간이 1시간에서 5분으로 줄었습니다.

"자동 생성된 노트를 살짝 다듬기만 하면 돼요. 이제 릴리즈가 두렵지 않아요!"

실전 팁

💡 - 커밋 메시지는 "무엇을" 했는지 명확하게 작성하세요 (왜는 PR 설명에)

  • commitlint를 사용하면 규칙에 맞지 않는 커밋을 방지할 수 있습니다
  • BREAKING CHANGE는 사용자에게 매우 중요한 정보이니 빠뜨리지 마세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Flutter#GitHubActions#CI/CD#Fastlane#AppStore#Flutter,Flame,Game

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

AAA급 게임 프로젝트 완벽 가이드

Flutter와 Flame 엔진을 활용하여 AAA급 퀄리티의 모바일 게임을 개발하는 전체 과정을 다룹니다. 기획부터 앱 스토어 출시까지, 실무에서 필요한 모든 단계를 이북처럼 술술 읽히는 스타일로 설명합니다.

게임 분석과 메트릭스 완벽 가이드

Flutter와 Flame으로 개발한 게임의 성공을 측정하고 개선하는 방법을 배웁니다. Firebase Analytics 연동부터 A/B 테스팅, 리텐션 분석까지 데이터 기반 게임 운영의 모든 것을 다룹니다.

게임 보안과 치팅 방지 완벽 가이드

Flutter와 Flame 게임 엔진에서 클라이언트 보안부터 서버 검증까지, 치터들로부터 게임을 보호하는 핵심 기법을 다룹니다. 초급 개발자도 쉽게 따라할 수 있는 실전 보안 코드와 함께 설명합니다.

CI/CD 파이프라인 통합 완벽 가이드

Jenkins, GitLab CI와 Ansible을 연동하여 자동화된 배포 파이프라인을 구축하는 방법을 다룹니다. Ansible Tower/AWX의 활용법과 실무에서 바로 적용 가능한 워크플로우 설계 패턴을 단계별로 설명합니다.

애니메이션 시스템 커스터마이징 완벽 가이드

Flutter와 Flame 게임 엔진에서 고급 애니메이션 시스템을 구현하는 방법을 다룹니다. 스켈레탈 애니메이션부터 절차적 애니메이션까지, 게임 개발에 필요한 핵심 애니메이션 기법을 실무 예제와 함께 배워봅니다.