Developer Tools

GitHub Actions CI/CD: 처음부터 시작하는 빌드, 테스트, 배포 워크플로우

지속적 통합 및 배포를 위한 GitHub Actions 설정 방법을 알아보세요. 트리거, 잡, 매트릭스, 시크릿, 캐싱, Docker, 실제 워크플로우 예시를 다룹니다.

10분 읽기

화면에 표시된 GitHub 코드 협업

GitHub Actions는 모든 푸시, 풀 리퀘스트, 머지를 자동화된 파이프라인으로 전환합니다. 테스트 실행, 아티팩트 빌드, 취약점 스캔, 프로덕션 배포까지 — 관리할 인프라 없이 저장소 안에서 바로 동작하는 CI/CD 플랫폼입니다.

GitHub Actions 동작 원리

모든 워크플로우는 .github/workflows/ 안의 YAML 파일입니다. 트리거 이벤트가 발생하면 GitHub가 새로운 가상 머신을 실행하고, 잡을 처리한 뒤 결과를 보고합니다 — 모두 수 초 안에 이루어집니다.

main에 푸시
  ↓
워크플로우 트리거됨
  ↓
잡: 빌드 & 테스트 (ubuntu-latest)
  ├── 1단계: 코드 체크아웃
  ├── 2단계: Node.js 설치
  ├── 3단계: npm ci
  ├── 4단계: npm test
  └── 5단계: npm run build
  ↓
✅ 모든 검사 통과

첫 번째 워크플로우

.github/workflows/ci.yml 파일을 생성하세요:

name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm test
      - run: npm run build

이게 전부입니다. 이제 main에 대한 모든 푸시와 PR에서 테스트 스위트가 자동으로 실행됩니다.

트리거: 워크플로우 실행 시점

코드 이벤트

on:
  push:
    branches: [main, develop]
    paths: ['src/**', 'package.json']  # 해당 파일이 변경될 때만 실행
  pull_request:
    types: [opened, synchronize]

스케줄 (cron)

on:
  schedule:
    - cron: '0 6 * * 1'  # 매주 월요일 UTC 06:00

수동 실행

on:
  workflow_dispatch:
    inputs:
      environment:
        description: '배포 대상'
        required: true
        type: choice
        options: [staging, production]

이렇게 하면 GitHub UI에 "Run workflow" 버튼이 추가되어 파라미터를 선택할 수 있습니다.

재사용 가능한 워크플로우

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'

다른 워크플로우에서 uses: ./.github/workflows/reusable.yml로 호출할 수 있습니다.

잡과 스텝

병렬 잡

잡은 기본적으로 병렬로 실행됩니다:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  build:
    runs-on: ubuntu-latest
    needs: [lint, test]  # 두 잡이 모두 통과할 때까지 대기
    steps:
      - uses: actions/checkout@v4
      - run: npm run build

매트릭스 전략 — 여러 버전에서 테스트

동일한 잡을 여러 환경 설정에서 실행합니다:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
        os: [ubuntu-latest, windows-latest]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

이렇게 하면 6개의 병렬 잡이 생성됩니다 (버전 3개 × 운영체제 2개).

시크릿과 환경 변수

시크릿 사용

Settings → Secrets and variables → Actions에 민감한 값을 저장하세요:

steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    run: ./deploy.sh

시크릿은 로그에서 마스킹 처리되며, 포크된 저장소에는 노출되지 않습니다.

환경 변수

# 워크플로우 레벨
env:
  NODE_ENV: production

jobs:
  build:
    # 잡 레벨
    env:
      CI: true
    steps:
      - name: Build
        # 스텝 레벨
        env:
          VITE_API_URL: https://api.example.com
        run: npm run build

더 빠른 빌드를 위한 캐싱

캐싱 없이는 매 실행마다 모든 의존성을 새로 다운로드합니다. 캐싱을 적용하면 빌드 시간이 50~80% 단축됩니다:

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: npm  # npm 캐싱 내장

# 또는 다른 도구를 위한 수동 캐싱
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

아티팩트: 잡 간 데이터 공유

한 잡에서 빌드 결과물을 업로드하고 다른 잡에서 다운로드합니다:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
      - run: ./deploy.sh

실제 워크플로우 예시

Docker 빌드 및 푸시

name: Docker

on:
  push:
    tags: ['v*']

permissions:
  packages: write

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Cloudflare Pages에 배포

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci && npm run build
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          command: pages deploy dist --project-name=my-site

체인지로그와 함께 릴리스

name: Release

on:
  push:
    tags: ['v*.*.*']

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Generate changelog
        run: |
          git log $(git describe --tags --abbrev=0 HEAD^)..HEAD \
            --pretty=format:"- %s" > CHANGELOG.md
      - uses: softprops/action-gh-release@v2
        with:
          body_path: CHANGELOG.md
          generate_release_notes: true

조건부 실행

스텝과 잡이 실행되는 조건을 제어합니다:

steps:
  - name: Deploy to production
    if: github.ref == 'refs/heads/main'
    run: ./deploy-prod.sh

  - name: Deploy to staging
    if: github.event_name == 'pull_request'
    run: ./deploy-staging.sh

  - name: Notify on failure
    if: failure()
    run: curl -X POST ${{ secrets.SLACK_WEBHOOK }} -d '{"text":"Build failed!"}'

보안 모범 사례

1. 액션 버전을 SHA에 고정

# ❌ 변경 가능한 태그 — 보안 위협에 노출될 수 있음
- uses: actions/checkout@v4

# ✅ 정확한 커밋 SHA에 고정
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11

2. 최소 권한 원칙 적용

permissions:
  contents: read    # 필요한 것만
  packages: write

3. 시크릿을 절대 출력하지 않기

# ❌ 절대 이렇게 하지 마세요
- run: echo ${{ secrets.API_KEY }}

# ✅ 환경 변수 사용
- env:
    API_KEY: ${{ secrets.API_KEY }}
  run: ./script-that-uses-api-key.sh

4. 포크에서 오는 PR 권한 제한

on:
  pull_request_target:  # 베이스 브랜치 컨텍스트에서 실행
    types: [opened, synchronize]

실패한 워크플로우 디버깅

  1. 로그 확인 — 실패한 스텝을 클릭하면 전체 출력을 볼 수 있습니다
  2. 디버그 로깅 추가 — 시크릿 ACTIONS_STEP_DEBUGtrue로 설정하세요
  3. act로 로컬 실행nektos/act를 사용해 내 머신에서 워크플로우를 실행하세요
  4. 러너에 SSH 접속 — 인터랙티브 디버깅을 위해 mxschmitt/action-tmate를 사용하세요

워크플로우를 시각적으로 빌드하기

YAML을 직접 작성하고 싶지 않으신가요? **GitHub Actions Generator**를 사용해 Node.js, Python, Docker, 배포 등 다양한 템플릿으로 워크플로우를 시각적으로 구성하고, 프로덕션 바로 적용 가능한 YAML을 다운로드하세요.

마무리

GitHub Actions는 저장소 안에서 바로 동작하는 CI/CD를 제공합니다:

  1. 단순하게 시작 — 빌드 + 테스트가 포함된 워크플로우 파일 하나로 시작하세요
  2. 캐싱 추가actions/cache나 언어별 내장 캐싱으로 빌드 시간을 단축하세요
  3. 매트릭스 활용 — 다양한 버전과 운영체제에서 테스트하세요
  4. 시크릿 보호 — 하드코딩하지 말고 항상 암호화된 시크릿을 사용하세요
  5. 모든 것을 자동화 — 릴리스, 배포, 보안 스캔, 스케줄 작업까지

최고의 CI/CD 파이프라인은 여러분이 신경 쓰지 않아도 모든 커밋에서 자동으로 실행되는 것입니다. 한 번만 설정해 두면, GitHub가 나머지를 알아서 처리합니다.