GitHub Actions를 활용하면 코드 푸시부터 배포까지 전 과정을 자동화할 수 있습니다. 이번 포스트에서는 실전에서 바로 사용할 수 있는 CI/CD 파이프라인 구축 방법을 다룹니다.
1. CI/CD 개념 이해하기
CI (Continuous Integration, 지속적 통합)
개발자가 코드를 커밋할 때마다 자동으로 빌드와 테스트를 실행하는 프로세스입니다.
주요 목적:
- 코드 통합 과정에서 발생하는 버그를 조기 발견
- 빌드 실패나 테스트 실패를 즉시 파악
- 코드 품질 유지 및 향상
CD (Continuous Deployment/Delivery, 지속적 배포)
테스트를 통과한 코드를 자동으로 프로덕션 환경에 배포하는 프로세스입니다.
Continuous Delivery vs Continuous Deployment:
- Delivery: 배포 준비까지 자동화, 최종 배포는 수동 승인
- Deployment: 배포까지 완전 자동화
2. GitHub Actions 기본 구조
핵심 개념
# .github/workflows/example.yml
name: CI/CD Pipeline # 워크플로우 이름
on: [push, pull_request] # 트리거 이벤트
jobs: # 실행할 작업들
build: # Job 이름
runs-on: ubuntu-latest # Runner (실행 환경)
steps: # 순차적으로 실행할 단계들
- name: Checkout code
uses: actions/checkout@v4
- name: Run tests
run: npm test
주요 구성 요소:
- Workflow:
.github/workflows/디렉토리의 YAML 파일 - Job: 독립적으로 실행되는 작업 단위 (병렬 실행 가능)
- Step: Job 내에서 순차적으로 실행되는 명령
- Runner: 워크플로우를 실행하는 서버 (ubuntu-latest, windows-latest 등)
3. YAML 문법 기초
on: 워크플로우 트리거
# 단일 이벤트
on: push
# 여러 이벤트
on: [push, pull_request]
# 세부 설정
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
jobs: 작업 정의
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
deploy:
needs: test # test job 완료 후 실행
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
with: 액션 파라미터 전달
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
env: 환경변수 설정
env:
NODE_ENV: production
jobs:
build:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
steps:
- run: echo $NODE_ENV
4. 첫 번째 워크플로우 작성
간단한 Node.js 프로젝트의 빌드 및 테스트 자동화:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: 빌드
run: npm run build
- name: 테스트 실행
run: npm test
주요 포인트:
npm ci:npm install보다 빠르고 일관된 설치 (CI 환경에 최적화)cache: 'npm': node_modules 캐싱으로 설치 속도 향상- PR 생성 시에도 자동으로 테스트 실행
5. Spring Boot CI 파이프라인
Gradle 기반 Spring Boot 프로젝트의 빌드 및 테스트:
# .github/workflows/spring-boot-ci.yml
name: Spring Boot CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: JDK 17 설정
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Gradle 실행 권한 부여
run: chmod +x gradlew
- name: Gradle 빌드
run: ./gradlew build
- name: 테스트 실행
run: ./gradlew test
- name: JaCoCo 테스트 커버리지 리포트
run: ./gradlew jacocoTestReport
- name: 테스트 결과 업로드
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: build/test-results/
- name: 커버리지 리포트 업로드
uses: actions/upload-artifact@v4
with:
name: jacoco-report
path: build/reports/jacoco/
JaCoCo 설정 (build.gradle):
plugins {
id 'jacoco'
}
jacoco {
toolVersion = "0.8.11"
}
test {
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
}
}
6. React CI 파이프라인
Vite 기반 React 프로젝트의 린트, 테스트, 빌드:
# .github/workflows/react-ci.yml
name: React CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
lint-test-build:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: ESLint 검사
run: npm run lint
- name: 타입 체크 (TypeScript)
run: npm run type-check
continue-on-error: false
- name: 테스트 실행
run: npm test -- --coverage
- name: 프로덕션 빌드
run: npm run build
- name: 빌드 결과물 업로드
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
package.json 스크립트 예시:
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"type-check": "tsc --noEmit",
"test": "vitest",
"preview": "vite preview"
}
}
7. Docker 이미지 빌드 & GitHub Container Registry 푸시
빌드한 애플리케이션을 Docker 이미지로 만들고 GHCR에 푸시:
# .github/workflows/docker-build.yml
name: Docker Build & Push
on:
push:
branches: [ main ]
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Docker Buildx 설정
uses: docker/setup-buildx-action@v3
- name: GHCR 로그인
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 메타데이터 추출
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Docker 이미지 빌드 및 푸시
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
Dockerfile 예시 (Spring Boot):
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
주요 포인트:
GITHUB_TOKEN: 자동으로 제공되는 시크릿 (별도 설정 불필요)cache-from/cache-to: 빌드 캐시로 속도 향상metadata-action: 브랜치명, 태그, SHA 기반으로 이미지 태그 자동 생성
8. 환경별 배포 전략
환경변수와 Secrets 관리
GitHub Secrets 설정:
- Repository → Settings → Secrets and variables → Actions
New repository secret클릭- Name과 Value 입력 (예:
DATABASE_URL,API_KEY)
Environment 기반 배포:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches:
- develop
- main
jobs:
deploy-dev:
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: development
steps:
- name: 개발 환경 배포
run: |
echo "배포 대상: ${{ vars.DEPLOY_URL }}"
echo "API_KEY: ${{ secrets.API_KEY }}"
deploy-prod:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: 프로덕션 배포
run: |
echo "배포 대상: ${{ vars.DEPLOY_URL }}"
echo "API_KEY: ${{ secrets.API_KEY }}"
Environment 설정 방법:
- Repository → Settings → Environments
New environment클릭 (development, staging, production)- Environment secrets 및 variables 설정
- (선택) Protection rules 설정 (승인 필요, 특정 브랜치만 허용)
다중 환경 배포 워크플로우
# .github/workflows/multi-env-deploy.yml
name: Multi-Environment Deploy
on:
workflow_dispatch:
inputs:
environment:
description: '배포 환경'
required: true
type: choice
options:
- development
- staging
- production
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: 환경 정보 출력
run: |
echo "Environment: ${{ github.event.inputs.environment }}"
echo "Deploy URL: ${{ vars.DEPLOY_URL }}"
- name: Docker 이미지 배포
run: |
docker pull ghcr.io/${{ github.repository }}:latest
# 배포 스크립트 실행
주요 포인트:
workflow_dispatch: 수동으로 워크플로우 실행 가능environment: 환경별로 다른 시크릿/변수 사용vars.VARIABLE_NAME: 환경 변수 참조secrets.SECRET_NAME: 환경 시크릿 참조
9. 실전 워크플로우 예제
PR 생성 시 테스트 → main 머지 시 자동 배포:
# .github/workflows/pr-main-deploy.yml
name: PR Test & Main Deploy
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
test:
name: 테스트 실행
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: 린트 검사
run: npm run lint
- name: 테스트 실행
run: npm test -- --coverage
- name: 빌드 검증
run: npm run build
build-and-push:
name: Docker 이미지 빌드 및 푸시
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: GHCR 로그인
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker 이미지 빌드 및 푸시
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
deploy:
name: 프로덕션 배포
needs: build-and-push
runs-on: ubuntu-latest
environment: production
steps:
- name: 서버 배포
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /app
docker pull ghcr.io/${{ github.repository }}:latest
docker-compose down
docker-compose up -d
docker image prune -f
- name: 배포 완료 알림
run: |
echo "✅ 배포 완료: https://${{ vars.DEPLOY_URL }}"
Spring Boot + React 풀스택 예제
# .github/workflows/fullstack-cicd.yml
name: Fullstack CI/CD
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
test-backend:
name: 백엔드 테스트
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: JDK 설정
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: 백엔드 테스트
run: |
cd backend
chmod +x gradlew
./gradlew test
test-frontend:
name: 프론트엔드 테스트
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: 프론트엔드 테스트
run: |
cd frontend
npm ci
npm run lint
npm test
npm run build
deploy:
name: 통합 배포
needs: [test-backend, test-frontend]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Docker Compose 배포
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /app
git pull origin main
docker-compose build
docker-compose down
docker-compose up -d
docker-compose.yml 예시:
version: '3.8'
services:
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DATABASE_URL=${DATABASE_URL}
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
마무리
GitHub Actions를 활용한 CI/CD 파이프라인 구축의 핵심은 다음과 같습니다:
- 작은 단위로 시작: 간단한 테스트 자동화부터 시작하여 점진적으로 확장
- 실패 빠르게 파악: PR 단계에서 문제를 조기 발견
- 환경 분리: 개발/스테이징/프로덕션 환경을 명확히 구분
- 보안 관리: Secrets로 민감 정보 보호
- 캐싱 활용: 빌드 시간 단축으로 개발자 경험 향상
다음 단계로는 다음 주제들을 고려해볼 수 있습니다:
- Blue-Green 배포 전략
- Canary 배포로 점진적 롤아웃
- 자동 롤백 메커니즘
- Slack/Discord 알림 연동
- 성능 테스트 자동화
실제 프로젝트에 적용하면서 팀의 상황에 맞게 커스터마이징해보세요.