Ch 03. GitHub Actions로 CI·CD 구성
코드를 푸시할 때마다 수동으로 테스트하고, 빌드하고, 배포하는 것은 번거롭고 실수가 생기기 쉽습니다. GitHub Actions로 이 과정을 자동화합니다. 테스트 실패 시 배포가 차단되고, 성공 시 자동으로 배포까지 이어집니다.
CI 워크플로우 — 테스트 자동화
main 브랜치에 PR을 올리거나 푸시할 때마다 테스트가 자동 실행됩니다.
# 새 파일: .github/workflows/ci.ymlname: CIon: push: branches: [main] pull_request: branches: [main]jobs: test: name: Test runs-on: ubuntu-latest steps: - name: 코드 체크아웃 uses: actions/checkout@v4 - name: Dart SDK 설정 uses: dart-lang/setup-dart@v1 with: sdk: stable - name: 의존성 설치 run: dart pub get - name: 정적 분석 run: dart analyze - name: 코드 포맷 확인 run: dart format --output=none --set-exit-if-changed . - name: 테스트 실행 (커버리지 포함) run: dart test --coverage=coverage - name: 커버리지 변환 run: | dart pub global activate coverage dart pub global run coverage:format_coverage \ --lcov \ --in=coverage \ --out=coverage/lcov.info \ --packages=.dart_tool/package_config.json \ --report-on=lib - name: Codecov 업로드 uses: codecov/codecov-action@v3 with: file: coverage/lcov.info
dart format --set-exit-if-changed는 포맷이 맞지 않으면 빌드를 실패시킵니다. 팀 전체가 동일한 코드 스타일을 유지하도록 강제합니다.
멀티 패키지 CI
이 교재의 프로젝트는 여러 패키지로 나뉩니다. 각 패키지를 별도 job으로 테스트합니다.
# 새 파일: .github/workflows/ci-all.ymlname: CI All Packageson: push: branches: [main] pull_request: branches: [main]jobs: test-dart-validator: name: dart_validator 테스트 runs-on: ubuntu-latest defaults: run: working-directory: dart_validator steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 with: sdk: stable - run: dart pub get - run: dart analyze - run: dart test --coverage=coverage test-todo-server: name: todo_server 테스트 runs-on: ubuntu-latest defaults: run: working-directory: todo_server steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 with: sdk: stable - run: dart pub get - run: dart analyze - run: dart test
defaults.run.working-directory로 각 job의 기본 작업 디렉토리를 지정합니다.
CD 워크플로우 — Docker 이미지 빌드 및 배포
main 브랜치에 머지될 때 Docker 이미지를 빌드하고 GitHub Container Registry에 푸시합니다.
# 새 파일: .github/workflows/deploy.ymlname: Deployon: push: branches: [main] paths: - 'todo_server/**'jobs: build-and-push: name: Docker 빌드 및 푸시 runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: 코드 체크아웃 uses: actions/checkout@v4 - name: GitHub Container Registry 로그인 uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Docker 메타데이터 설정 id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }}/todo-server tags: | type=sha,prefix=sha- type=raw,value=latest - name: Docker Buildx 설정 uses: docker/setup-buildx-action@v3 - name: 이미지 빌드 및 푸시 uses: docker/build-push-action@v5 with: context: ./todo_server push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max deploy: name: 서버 배포 needs: build-and-push runs-on: ubuntu-latest environment: production steps: - name: SSH로 서버 배포 uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} script: | docker pull ghcr.io/${{ github.repository }}/todo-server:latest docker-compose -f /opt/todo-app/docker-compose.yml up -d todo-server docker image prune -f
${{ secrets.GITHUB_TOKEN }}은 GitHub Actions가 자동으로 제공합니다. SERVER_HOST, SERVER_USER, SERVER_SSH_KEY는 GitHub 저장소의 Settings → Secrets에서 설정합니다.
cache-from/cache-to: type=gha는 GitHub Actions 캐시를 Docker 레이어 캐시로 활용합니다. 의존성이 바뀌지 않으면 dart pub get 레이어가 캐시되어 빌드 시간이 크게 줄어듭니다.
pub.dev 자동 퍼블리시
dart_validator 패키지의 버전 태그를 푸시하면 자동으로 pub.dev에 배포합니다.
# 새 파일: dart_validator/.github/workflows/publish.ymlname: Publish to pub.devon: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+'jobs: publish: name: pub.dev 배포 runs-on: ubuntu-latest permissions: id-token: write # OIDC 토큰 필요 steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 with: sdk: stable - name: 의존성 설치 working-directory: dart_validator run: dart pub get - name: 테스트 실행 working-directory: dart_validator run: dart test - name: pub.dev 배포 working-directory: dart_validator run: dart pub publish --force
permissions.id-token: write는 GitHub OIDC 토큰을 이용하여 pub.dev 인증을 처리합니다. 비밀번호나 API 키 없이 안전하게 배포할 수 있습니다. pub.dev에서 해당 저장소를 "trusted publisher"로 등록해야 합니다.
브랜치 보호 규칙 설정
CI가 통과되지 않으면 main 브랜치에 머지할 수 없도록 설정합니다.
GitHub 저장소 → Settings → Branches → Branch protection rules에서 설정합니다.
- Branch name pattern:
main - Require status checks to pass before merging: 체크
- Status checks:
Test(CI 워크플로우의 job 이름) - Require branches to be up to date before merging: 체크
이 설정이 있으면 테스트를 통과하지 못한 코드는 main에 들어올 수 없습니다.
Actions 실행 시간 절약 팁
GitHub Actions 무료 사용량(월 2,000분)을 효율적으로 사용합니다.
첫째, paths 필터로 변경된 패키지만 테스트합니다.
on: push: paths: - 'dart_validator/**'
둘째, 의존성 캐시를 사용합니다.
- name: pub 캐시 uses: actions/cache@v3 with: path: ~/.pub-cache key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
셋째, 빠른 작업부터 실행합니다. dart analyze는 dart test보다 빠릅니다. 분석에서 실패하면 테스트까지 실행하지 않아도 됩니다.
이번 챕터 정리
- CI 워크플로우로 PR마다 분석, 포맷 확인, 테스트를 자동 실행했습니다.
- CD 워크플로우로
main머지 시 Docker 이미지 빌드 → GHCR 푸시 → 서버 배포를 자동화했습니다. - GitHub OIDC로 API 키 없이 pub.dev 자동 배포를 구성했습니다.
- 브랜치 보호 규칙으로 테스트 통과를 머지 조건으로 강제했습니다.
다음 챕터에서는 Dart DevTools로 성능을 분석하고 최적화합니다.