GitHub Actions는 GitHub에 내장된 CI/CD 플랫폼입니다. 코드 푸시부터 프로덕션 배포까지 모든 워크플로우를 자동화할 수 있습니다.
GitHub Actions 개념
| 개념 | 설명 |
|---|---|
| Workflow | 자동화 프로세스 전체 (.yml 파일) |
| Event | 워크플로우를 트리거하는 이벤트 |
| Job | 같은 러너에서 실행되는 스텝 모음 |
| Step | 개별 작업 단위 (액션 또는 스크립트) |
| Action | 재사용 가능한 작업 단위 |
| Runner | 워크플로우를 실행하는 서버 |
기본 워크플로우
파일 위치
my-repo/
└── .github/
└── workflows/
├── ci.yml
├── deploy.yml
└── release.yml
첫 번째 워크플로우
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linter
run: npm run lint
주요 트리거 (Events)
push / pull_request
on:
push:
branches:
- main
- 'release/**'
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
수동 실행
on:
workflow_dispatch:
inputs:
environment:
description: '배포 환경'
required: true
default: 'staging'
type: choice
options:
- staging
- production
debug:
description: '디버그 모드'
required: false
type: boolean
default: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to ${{ inputs.environment }}"
- if: ${{ inputs.debug }}
run: echo "Debug mode enabled"
스케줄 (Cron)
on:
schedule:
# 매일 자정 (UTC)
- cron: '0 0 * * *'
# 매주 월요일 오전 9시 (KST = UTC+9)
- cron: '0 0 * * 1'
다른 워크플로우 호출
# 호출하는 쪽
on:
workflow_run:
workflows: ["Build"]
types: [completed]
branches: [main]
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- run: echo "Build succeeded, deploying..."
실전 워크플로우
Next.js CI/CD
name: Next.js CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
# 1. 린트 & 타입 체크
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run type-check
# 2. 테스트
test:
runs-on: ubuntu-latest
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: Upload coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
# 3. 빌드
build:
runs-on: ubuntu-latest
needs: [lint, 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: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: .next/
retention-days: 7
# 4. 배포 (main 브랜치만)
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build
path: .next/
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Docker 빌드 & 푸시
name: Docker Build
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
매트릭스 빌드
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 18
include:
- os: ubuntu-latest
node: 20
coverage: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
- if: matrix.coverage
run: npm run test:coverage
비밀 관리
Secrets 설정
Secrets 사용
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
echo "Deploying with API key"
# API_KEY와 DATABASE_URL 사용
Environment Variables
env:
# 워크플로우 레벨
NODE_ENV: production
jobs:
build:
runs-on: ubuntu-latest
env:
# Job 레벨
CI: true
steps:
- name: Build
env:
# Step 레벨
NEXT_PUBLIC_API_URL: ${{ vars.API_URL }}
run: npm run build
캐싱 전략
npm 캐시
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # 자동 캐싱
커스텀 캐시
- name: Cache Next.js
uses: actions/cache@v4
with:
path: |
~/.npm
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.ts') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
${{ runner.os }}-nextjs-
Turborepo 캐시
- name: Cache Turbo
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
재사용 가능한 워크플로우
정의 (Callable Workflow)
# .github/workflows/reusable-build.yml
name: Reusable Build
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
environment:
required: true
type: string
secrets:
npm-token:
required: true
jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://npm.pkg.github.com'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
- run: npm run build
호출
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '20'
environment: production
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
유용한 액션들
# PR 자동 라벨링
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# Slack 알림
- uses: slackapi/slack-github-action@v1
with:
channel-id: 'C123456'
slack-message: "Build ${{ job.status }}: ${{ github.event.head_commit.message }}"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
# PR 코멘트
- uses: marocchino/sticky-pull-request-comment@v2
with:
message: |
## 빌드 결과
- 빌드: ✅
- 테스트: ✅
# 자동 병합
- uses: peter-evans/enable-pull-request-automerge@v3
with:
pull-request-number: ${{ github.event.pull_request.number }}
merge-method: squash
최적화 팁
1. 병렬 실행
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test:
runs-on: ubuntu-latest
steps: [...]
build:
needs: [lint, test] # lint와 test는 병렬, build는 이후
runs-on: ubuntu-latest
steps: [...]
2. 조건부 실행
steps:
- name: Only on main
if: github.ref == 'refs/heads/main'
run: npm run deploy
- name: Skip on bot commits
if: "!contains(github.event.head_commit.author.name, 'bot')"
run: npm test
- name: On failure
if: failure()
run: echo "Previous step failed"
3. 타임아웃 설정
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Long running test
timeout-minutes: 5
run: npm test
비용 절약
무료 한도 (Public 저장소: 무제한)
Private 저장소:
- GitHub Free: 2,000분/월
- GitHub Pro: 3,000분/월
- GitHub Team: 3,000분/월
비용 절약 팁
# 1. 불필요한 실행 방지
on:
push:
paths-ignore:
- '**.md'
- 'docs/**'
# 2. 작은 러너 사용
jobs:
test:
runs-on: ubuntu-latest # 가장 저렴
# 3. 캐시 적극 활용
- uses: actions/cache@v4
마치며
GitHub Actions 핵심 포인트:
- 간단 시작: 기본 CI부터 시작해서 점진적으로 확장
- 캐싱 필수: 빌드 시간과 비용 절약
- 재사용: Reusable Workflows로 중복 제거
- 보안: Secrets와 Environments 적극 활용
GitHub 저장소라면 GitHub Actions가 가장 자연스러운 선택입니다.