코드를 작성하고, 테스트하고, 빌드하고, 배포하는 과정을 매번 손으로 반복하는 것은 비효율적이고 실수를 유발합니다. CI/CD(Continuous Integration / Continuous Delivery)는 이 과정을 자동화해 팀이 코드 품질에 더 집중할 수 있게 해줍니다.
GitHub Actions는 GitHub 저장소에 내장된 CI/CD 플랫폼으로, 별도 서버 없이 워크플로 파일 하나만으로 자동화를 구현할 수 있습니다. 이 글에서는 Node.js 프로젝트를 기준으로 테스트 → 빌드 → 배포까지 완전한 파이프라인을 단계별로 만들어봅니다.
핵심 개념 — Workflow, Job, Step
GitHub Actions의 구조를 이해하는 것이 먼저입니다.
- Workflow — 하나의 자동화 프로세스 전체.
.github/workflows/폴더의 YAML 파일로 정의합니다. - Event — 워크플로를 트리거하는 이벤트.
push,pull_request,schedule등이 있습니다. - Job — 하나의 가상 머신(Runner)에서 실행되는 작업 단위. Job은 기본적으로 병렬 실행됩니다.
- Step — Job 내의 개별 명령. 셸 명령이나 Action을 실행합니다.
- Action — 재사용 가능한 Step의 묶음. GitHub Marketplace에서 수천 개를 찾을 수 있습니다.
기본 워크플로 작성하기
PR이 열리거나 main 브랜치에 push될 때 테스트를 실행하는 워크플로입니다.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22] # 여러 Node 버전에서 병렬 테스트
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Node.js ${{ matrix.node-version }} 설정
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: 린트 실행
run: npm run lint
- name: 테스트 실행
run: npm test
캐시로 속도 올리기
actions/setup-node의 cache: 'npm' 옵션은 node_modules를 캐싱해줍니다. 대규모 프로젝트에서는 빌드 시간을 수 분 단축할 수 있습니다. 더 세밀한 캐시 제어가 필요하다면 actions/cache를 직접 사용합니다.
- name: npm 캐시 복원
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
빌드 산출물(Artifact) 저장
빌드된 파일을 다음 Job이나 다운로드를 위해 저장할 수 있습니다.
build:
needs: test # test Job이 성공해야만 실행
runs-on: ubuntu-latest
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: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7
Secrets로 민감 정보 관리하기
API 키, 배포 토큰 같은 민감 정보는 절대 코드에 직접 쓰지 않습니다. GitHub 저장소의 Settings → Secrets and variables → Actions에 등록하고 워크플로에서 참조합니다.
- name: Cloudflare Pages 배포
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx wrangler pages deploy dist/ --project-name=my-blog
전체 CI/CD 파이프라인 예시
테스트 → 빌드 → 스테이징 배포 → (승인 후) 프로덕션 배포까지 이어지는 완전한 파이프라인입니다.
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci && npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with: { name: dist, path: dist/ }
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v4
with: { name: dist, path: dist/ }
- name: 스테이징 배포
run: echo "스테이징 서버에 배포 중..."
# 실제 배포 명령으로 교체하세요
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production # GitHub 환경 보호 규칙으로 수동 승인 요구 가능
steps:
- uses: actions/download-artifact@v4
with: { name: dist, path: dist/ }
- name: 프로덕션 배포
run: echo "프로덕션 서버에 배포 중..."
유용한 트리거 패턴
상황에 맞는 트리거를 선택하면 불필요한 실행을 줄일 수 있습니다.
on:
# 특정 경로 변경 시에만 실행
push:
paths:
- 'src/**'
- 'package.json'
# 매일 오전 9시(KST) 실행
schedule:
- cron: '0 0 * * *' # UTC 기준, KST = UTC+9
# 수동 트리거 (입력값 포함)
workflow_dispatch:
inputs:
environment:
description: '배포 대상 환경'
required: true
default: staging
type: choice
options: [staging, production]
GitHub Actions의 핵심은 이벤트 → Job → Step의 계층 구조를 이해하는 것입니다.
needs로 Job 의존 관계를 설정하고, matrix로 병렬 테스트를 구성하며, secrets로 민감 정보를 안전하게 관리하세요. 작은 워크플로 하나로 시작해 점진적으로 파이프라인을 발전시켜나가는 것이 좋습니다.
매트릭스 전략 — 다중 환경 테스트
Node.js 버전, OS 등 여러 환경에서 동시에 테스트하려면 strategy.matrix를 사용합니다.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
# 3 os × 3 node = 9개 병렬 잡 자동 생성
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
재사용 가능한 워크플로
여러 리포지토리에서 동일한 워크플로를 공유하려면 workflow_call을 활용합니다.
# .github/workflows/reusable-test.yml
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
secrets:
NPM_TOKEN:
required: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci && npm test
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# 다른 워크플로에서 호출
jobs:
call-test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
환경(Environment) 보호 규칙으로 프로덕션 배포 안전하게
GitHub 리포지토리 Settings → Environments에서 production 환경을 만들고 보호 규칙을 설정하면, 해당 환경을 대상으로 하는 워크플로 실행에 수동 승인이나 브랜치 제한이 적용됩니다.
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production # 보호 규칙이 설정된 환경
url: https://myapp.com
# 자동 적용되는 규칙 예시:
# - required reviewers: 1명 이상 승인 필요
# - deployment branches: main 브랜치만 허용
steps:
- name: 프로덕션 배포
run: ./deploy.sh
env:
DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}
팀의 실수로 인한 의도치 않은 프로덕션 배포를 방지하는 데 매우 효과적입니다. Required reviewers를 설정하면 지정된 팀원이 승인해야만 배포가 진행됩니다.
GitHub Actions 비용 최적화 전략
GitHub Actions는 퍼블릭 레포지토리에서 무료이지만, 프라이빗 레포지토리에서는 실행 시간에 따라 과금됩니다. 팀이 커질수록 CI 비용이 예상보다 빠르게 늘어나는 경우가 많습니다. 비용을 줄이는 가장 효과적인 방법 중 하나는 캐싱입니다. actions/cache를 활용해 node_modules나 pip 패키지를 캐시하면, 의존성 설치 시간이 수 분에서 수십 초로 줄어들어 총 실행 시간을 대폭 단축할 수 있습니다. 불필요한 워크플로 실행도 줄여야 합니다. paths 필터를 사용해 관련 파일이 변경될 때만 워크플로가 실행되도록 제한하면 됩니다. 예를 들어 프론트엔드 코드만 바뀌었을 때 백엔드 테스트가 돌지 않게 경로 조건을 걸 수 있습니다.
자체 호스팅 러너(Self-hosted runner)도 비용 절감 수단이 될 수 있습니다. 팀 내 유휴 서버나 클라우드 VM을 러너로 등록하면 실행 시간 과금 없이 워크플로를 돌릴 수 있습니다. 단, 자체 러너는 보안 관리 부담이 따르므로, 외부 기여자의 풀 리퀘스트가 자동으로 자체 러너에서 실행되지 않도록 설정을 꼼꼼히 확인해야 합니다.
GitHub Actions로 자동화할 수 있는 추가 작업들
많은 팀이 CI/CD 파이프라인에만 집중하지만, GitHub Actions는 그 이상의 자동화가 가능합니다. 이슈 트리아지 자동화가 대표적입니다. 새 이슈가 열리면 특정 키워드를 감지해 자동으로 레이블을 붙이거나, 담당자를 지정하거나, 템플릿 응답을 남길 수 있습니다. 오래된 이슈나 풀 리퀘스트를 자동으로 닫는 stale 봇도 Actions로 쉽게 구현됩니다. 릴리스 노트 자동 생성도 유용합니다. 태그가 푸시될 때 커밋 히스토리를 분석해 카테고리별로 변경 사항을 정리한 릴리스 노트를 자동 작성하면, 개발자가 매번 수동으로 정리하는 수고를 덜 수 있습니다. 보안 감사도 자동화 대상입니다. npm audit이나 Snyk 액션을 주기적으로 실행해 취약한 의존성이 발견되면 자동으로 이슈를 생성하도록 구성할 수 있습니다.