GitHub Actions CI/CD 가이드 — 워크플로우부터 배포까지

GitHub Actions란?

GitHub Actions는 GitHub에 내장된 CI/CD(지속적 통합/배포) 플랫폼입니다. 코드를 push하면 자동으로 테스트, 빌드, 배포가 실행됩니다. 공장의 자동화 라인에 비유하면, 원재료(코드)가 들어오면 품질 검사(테스트) → 조립(빌드) → 출하(배포)가 자동으로 이루어지는 것입니다.

핵심 개념

개념설명비유
Workflow자동화 프로세스 전체공정 라인
Event워크플로우를 시작하는 이벤트시작 버튼
Job같은 러너에서 실행되는 작업 단위작업 스테이션
StepJob 안의 개별 명령작업 단계
Runner워크플로우를 실행하는 서버작업자
Action재사용 가능한 작업 단위조립 부품

기본 워크플로우 작성

# .github/workflows/ci.yml
name: CI Pipeline

# 트리거 이벤트 정의
on:
  push:
    branches: [main, develop]   # main, develop 브랜치 push 시
  pull_request:
    branches: [main]            # main 대상 PR 생성/수정 시

# 환경변수 (워크플로우 전체에서 사용)
env:
  NODE_VERSION: "22"

jobs:
  # Job 1: 코드 품질 검사
  lint:
    name: Lint & Type Check
    runs-on: ubuntu-latest      # Ubuntu 최신 러너에서 실행
    steps:
      # 소스 코드 체크아웃
      - uses: actions/checkout@v4

      # Node.js 설정 + 캐시
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"          # node_modules 캐시 (빌드 속도 향상)

      # 의존성 설치
      - run: npm ci             # ci는 lock 파일 기준 정확한 버전 설치

      # 린트 실행
      - run: npm run lint
        name: ESLint 검사

      # 타입 체크
      - run: npm run typecheck
        name: TypeScript 타입 검사

  # Job 2: 테스트
  test:
    name: Unit Tests
    runs-on: ubuntu-latest
    needs: lint                 # lint job이 성공한 후에만 실행
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"
      - run: npm ci
      - run: npm test -- --coverage
        name: 테스트 실행 (커버리지 포함)

      # 테스트 커버리지 결과 업로드
      - uses: actions/upload-artifact@v4
        if: always()            # 테스트 실패해도 결과 업로드
        with:
          name: coverage-report
          path: coverage/

  # Job 3: 빌드
  build:
    name: Build
    runs-on: ubuntu-latest
    needs: test                 # 테스트 성공 후 실행
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"
      - run: npm ci
      - run: npm run build
        name: 프로덕션 빌드

      # 빌드 결과물 저장
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
          retention-days: 7    # 7일간 보관

매트릭스 빌드: 여러 환경에서 동시 테스트

# 여러 Node.js 버전과 OS에서 동시 테스트
jobs:
  test-matrix:
    name: Test (${{ matrix.os }} / Node ${{ matrix.node-version }})
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        exclude:
          # macOS + Node 18 조합 제외 (불필요)
          - os: macos-latest
            node-version: 18
        include:
          # 특정 조합에 추가 설정
          - os: ubuntu-latest
            node-version: 22
            coverage: true      # 이 조합에서만 커버리지 수집

      fail-fast: false          # 하나 실패해도 나머지 계속 실행

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - run: npm ci
      - run: npm test
      # 커버리지 설정이 있는 조합에서만 실행
      - run: npm test -- --coverage
        if: matrix.coverage == true

시크릿과 환경변수

# 시크릿은 GitHub Settings → Secrets and Variables → Actions에서 설정

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production     # 환경별 시크릿 분리 가능

    steps:
      - uses: actions/checkout@v4

      # 시크릿 사용 — 로그에 자동 마스킹됨
      - name: 배포
        env:
          API_KEY: ${{ secrets.API_KEY }}         # 시크릿
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          APP_ENV: production                     # 일반 변수
        run: |
          echo "배포 환경: $APP_ENV"
          # $API_KEY, $DEPLOY_TOKEN은 로그에 *** 로 마스킹됨
          ./deploy.sh

자동 배포 워크플로우

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

on:
  push:
    branches: [main]           # main 브랜치 push 시만 배포

# 동시 배포 방지
concurrency:
  group: production-deploy
  cancel-in-progress: false    # 진행 중인 배포는 취소하지 않음

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com  # 배포 URL (GitHub에 표시)

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - run: npm ci
      - run: npm run build

      # Docker 이미지 빌드 & 푸시
      - name: Docker 빌드 및 푸시
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}

      # SSH로 서버 배포
      - name: 서버 배포
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /app
            docker compose pull
            docker compose up -d --remove-orphans
            echo "배포 완료: $(date)"

      # Slack 알림
      - name: 배포 성공 알림
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "배포 성공: ${{ github.repository }} (${{ github.sha }})"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

      # 배포 실패 알림
      - name: 배포 실패 알림
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "배포 실패: ${{ github.repository }} — 확인 필요!"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

PR 자동화

# .github/workflows/pr-check.yml
name: PR Checks

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  pr-check:
    runs-on: ubuntu-latest

    # PR에 권한 부여 (댓글 작성 등)
    permissions:
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - run: npm ci
      - run: npm run build

      # 빌드 크기 비교 댓글
      - name: 번들 크기 보고
        uses: andresz1/size-limit-action@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          # PR에 번들 크기 변화를 댓글로 표시

      # 자동 라벨 추가
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

캐싱으로 빌드 속도 향상

# 캐시 전략 — 의존성 설치 시간 단축
steps:
  # npm 캐시 (setup-node가 자동 처리)
  - uses: actions/setup-node@v4
    with:
      node-version: "22"
      cache: "npm"

  # 커스텀 캐시 — 빌드 캐시 등
  - name: Turbo 캐시
    uses: actions/cache@v4
    with:
      path: .turbo           # 캐시 경로
      key: turbo-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        turbo-${{ runner.os }}-
캐시 대상효과설정 방법
npm/yarn/pnpm의존성 설치 80% 단축setup-nodecache 옵션
Docker 레이어이미지 빌드 50% 단축docker/build-push-actioncache-from
빌드 결과물증분 빌드actions/cache.next/, dist/ 캐시

실전 팁

  • npm cinpm install 대신 사용하세요: CI 환경에서는 npm ci가 lock 파일 기준으로 정확한 버전을 설치하고, 더 빠릅니다.
  • concurrency 설정으로 동시 실행을 제어하세요: 같은 브랜치에서 여러 번 push하면 이전 실행을 자동 취소하여 리소스를 절약합니다.
  • if: always(), if: failure()를 활용하세요: 테스트 실패 시에도 리포트를 업로드하거나 알림을 보내는 후속 작업이 가능합니다.
  • Reusable Workflow로 중복을 줄이세요: 여러 리포지토리에서 공통 워크플로우를 uses: org/workflows/.github/workflows/ci.yml@main으로 재사용할 수 있습니다.
  • GitHub Actions Marketplace를 활용하세요: 대부분의 작업에 이미 만들어진 Action이 있습니다. 직접 스크립트를 작성하기 전에 검색해보세요.
  • 비용을 모니터링하세요: Public 리포지토리는 무료지만, Private 리포지토리는 월 2,000분 무료 후 과금됩니다. 불필요한 워크플로우를 정리하세요.

이 글이 도움이 되었나요?