0. 들어가며
지금까지 GitHub Actions의 이론, 기본 문법, 변수와 컨텍스트까지 모두 배웠다. 이번 포스팅에서는 배운 내용을 종합하여 실제 Spring Boot 애플리케이션의 CI/CD 파이프라인을 구축해보겠다.
- 코드 push 시 자동 빌드 및 테스트
- 테스트 통과 시 Docker 이미지 빌드
- Docker Hub에 이미지 push
- 배포 서버에 자동 배포
1. 프로젝트 준비
1.1. Spring Boot 애플리케이션
간단한 Spring Boot 애플리케이션을 준비한다.
프로젝트 구조:
spring-boot-ci-cd/
├── .github/
│ └── workflows/
│ └── ci-cd-pipeline.yml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── demo/
│ │ │ └── DemoApplication.java
│ │ └── resources/
│ │ └── application.yml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── demo/
│ └── DemoApplicationTests.java
├── Dockerfile
├── pom.xml
└── README.md
1.2. Dockerfile 작성
FROM openjdk:17-jdk-slim AS builder
WORKDIR /build
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
RUN ./mvnw dependency:go-offline
COPY src ./src
RUN ./mvnw package -DskipTests
FROM openjdk:17-jre-slim
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
1.3. GitHub Secrets 설정
파이프라인에 필요한 Secrets을 등록한다.
| Secret 이름 | 설명 |
| DOCKER_USERNAME | Docker Hub 사용자명 |
| DOCKER_PASSWORD | Docker Hub 비밀번호 또는 토큰 |
| DEPLOY_HOST | 배포 서버 IP 또는 도메인 |
| DEPLOY_USERNAME | 배포 서버 SSH 사용자명 |
| DEPLOY_SSH_KEY | 배포 서버 SSH 개인키 |
2. CI/CD 파이프라인 전체 구조
2.1. 파이프라인 설계
[Push / PR] → [Build & Test] → [Docker Build] → [Docker Push] → [Deploy]
↓ ↓ ↓ ↓ ↓
Trigger Run tests Create image Push to Hub Deploy to server
2.2. 전체 워크플로우 파일
.github/workflows/ci-cd-pipeline.yml
name: Spring Boot CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
environment:
description: '배포 환경'
required: true
default: 'dev'
type: choice
options:
- dev
- prod
env:
DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/spring-boot-app
JAVA_VERSION: '17'
jobs:
# 1. 테스트 Job
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: 'temurin'
cache: maven
- name: Run tests
run: ./mvnw test
- name: Generate test report
if: always()
uses: dorny/test-reporter@v1
with:
name: Maven Tests
path: target/surefire-reports/*.xml
reporter: java-junit
fail-on-error: false
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: target/surefire-reports/
# 2. 빌드 Job (test에 의존)
build:
name: Build
needs: test
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.action != 'closed'
outputs:
version: ${{ steps.get-version.outputs.version }}
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: ./mvnw package -DskipTests
- name: Get version from pom.xml
id: get-version
run: |
VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Project version: $VERSION"
- name: Upload JAR artifact
uses: actions/upload-artifact@v3
with:
name: app-jar
path: target/*.jar
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=raw,value=${{ steps.get-version.outputs.version }}
type=sha,prefix=,format=short
type=ref,event=branch
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
# 3. Docker Build & Push Job
docker:
name: Docker Build & Push
needs: build
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ needs.build.outputs.image-tag }}
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max
build-args: |
VERSION=${{ needs.build.outputs.version }}
- name: Image digest
run: echo "Image pushed with digest ${{ steps.build-push.outputs.digest }}"
# 4. 배포 Job (환경별)
deploy:
name: Deploy
needs: [build, docker]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: ${{ github.event.inputs.environment || 'prod' }}
steps:
- name: Deploy to Server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USERNAME }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
script: |
# 배포 스크립트
docker pull ${{ env.DOCKER_IMAGE }}:latest
docker stop spring-app || true
docker rm spring-app || true
docker run -d \\
--name spring-app \\
-p 8080:8080 \\
--restart unless-stopped \\
${{ env.DOCKER_IMAGE }}:latest
docker system prune -f
- name: Health check
run: |
sleep 10
curl -f <http://$>{{ secrets.DEPLOY_HOST }}:8080/actuator/health || exit 1
- name: Send Slack notification
if: always()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "배포 결과: ${{ job.status }}\\n환경: ${{ github.event.inputs.environment || 'prod' }}\\n버전: ${{ needs.build.outputs.version }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
3. 단계별 상세 설명
3.1. Test Job 분석
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven # Maven 의존성 캐싱 (빌드 속도 향상)
- run: ./mvnw test
- uses: dorny/test-reporter@v1
if: always() # 테스트 성공/실패 관계없이 리포트 생성
with:
name: Maven Tests
path: target/surefire-reports/*.xml
reporter: java-junit
핵심 포인트:
- cache: maven: Maven 의존성을 캐시하여 다음 실행 시 빌드 속도 향상
- if: always(): 테스트 실패해도 리포트는 생성하도록 설정
- test-reporter: 테스트 결과를 UI에서 보기 좋게 표시
3.2. Build Job 분석
build:
needs: test
outputs:
version: ${{ steps.get-version.outputs.version }}
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- run: ./mvnw package -DskipTests # 테스트는 이미 했으므로 건너뜀
- id: get-version
run: |
VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=raw,value=${{ steps.get-version.outputs.version }}
type=sha,prefix=,format=short
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
핵심 포인트:
- outputs: 다음 Job에서 사용할 값들을 정의
- metadata-action: Docker 태그를 자동 생성 (버전, 커밋 SHA, latest 등)
3.3. Docker Job 분석
docker:
needs: build
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
steps:
- uses: docker/setup-buildx-action@v3 # Buildx 설정 (멀티 플랫폼 빌드 가능)
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ needs.build.outputs.image-tag }}
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max
핵심 포인트:
- setup-buildx-action: Docker Buildx 설정 (멀티 아키텍처 빌드 가능)
- cache-from/to: 레이어 캐싱으로 빌드 속도 최적화
- push: true: 빌드 후 자동으로 Docker Hub에 push
3.4. Deploy Job 분석
deploy:
needs: [build, docker]
environment: ${{ github.event.inputs.environment || 'prod' }}
steps:
- uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USERNAME }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
docker pull ${{ env.DOCKER_IMAGE }}:latest
docker stop spring-app || true
docker rm spring-app || true
docker run -d --name spring-app -p 8080:8080 ${{ env.DOCKER_IMAGE }}:latest
- run: |
sleep 10
curl -f <http://$>{{ secrets.DEPLOY_HOST }}:8080/actuator/health || exit 1
핵심 포인트:
- environment: GitHub 환경 설정과 연동 (승인 절차, 보안)
- appleboy/ssh-action: SSH로 서버에 접속하여 명령 실행
- Health check: 배포 후 애플리케이션 정상 동작 확인
4. 환경별 배포 설정
4.1. GitHub Environments 설정
저장소 Settings > Environments에서 환경을 설정할 수 있다.
Environments:
- dev
- Required reviewers: (선택)
- Wait timer: (선택)
- Deployment branches: develop
- prod
- Required reviewers: 2명
- Wait timer: 10분
- Deployment branches: main
4.2. 환경별 워크플로우 확장
name: Environment-specific Deployment
on:
push:
branches: [ develop, main ]
jobs:
deploy-dev:
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: dev
steps:
- run: echo "개발 환경 배포"
deploy-prod:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: prod
steps:
- run: echo "운영 환경 배포"
5. 고급 최적화
5.1. 의존성 캐싱
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
5.2. 조건부 단계 실행
- name: Run integration tests
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: ./mvnw verify -Pintegration-tests
5.3. 병렬 테스트 실행
strategy:
matrix:
java: [11, 17, 21]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
- run: ./mvnw test
6. 모니터링 및 알림
6.1. Slack 알림 추가
- name: Notify Slack on success
if: success()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "✅ 배포 성공: ${{ github.repository }}@${{ github.sha }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*배포 성공*\\n저장소: ${{ github.repository }}\\n버전: ${{ needs.build.outputs.version }}\\n환경: ${{ github.event.inputs.environment || 'prod' }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
6.2. 배포 상태 배지 추가
README.md에 배지 추가:

7. 전체 파이프라인 실행 결과
7.1. 성공적인 실행
https://via.placeholder.com/800x400?text=CI%252FCD+Pipeline+Success
7.2. 로그 확인
GitHub Actions 탭에서 각 Step의 상세 로그를 확인할 수 있다.
Run ./mvnw test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:demo >--------------------
[INFO] Building demo 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/runner/work/demo/demo/target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory /home/runner/work/demo/demo/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/runner/work/demo/demo/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ demo ---
[INFO] Running com.example.demo.DemoApplicationTests
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.456 s - in com.example.demo.DemoApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12.345 s
[INFO] Finished at: 2024-01-15T09:30:45Z
[INFO] ------------------------------------------------------------------------
8. 정리
이번 포스팅에서는 실제 Spring Boot 애플리케이션의 CI/CD 파이프라인을 구축해보았다.
구현된 파이프라인:
- 테스트 자동화: 코드 push 시 자동으로 테스트 실행
- 빌드 자동화: 테스트 통과 시 JAR 파일 빌드
- Docker 이미지 빌드 및 Push: Docker Hub에 이미지 업로드
- 자동 배포: 서버에 SSH 접속하여 최신 이미지로 배포
- 헬스 체크: 배포 후 애플리케이션 정상 동작 확인
- 알림: Slack으로 배포 결과 전송
배운 점:
- Job 간 의존성(needs)으로 파이프라인 순서 제어
- outputs로 Job 간 데이터 공유
- Secrets로 민감 정보 안전하게 관리
- 환경별(environment) 배포 구성
- 캐싱으로 빌드 속도 최적화
'CI&CD > GitHub Actions' 카테고리의 다른 글
| [ADVANED #1] GitHub Actions에서 Docker 고급 빌드 전략 (Buildx, 캐싱, 보안) (0) | 2026.02.24 |
|---|---|
| [BASIC #4] GitHub Actions 변수와 컨텍스트 마스터하기 (0) | 2026.02.24 |
| [BASIC #3] GitHub Actions 핵심 문법 완벽 가이드 (0) | 2025.09.23 |
| [BASIC #2] 워크플로우 생성 및 실행 실습 (0) | 2025.06.24 |
| [BASIC #1] CI/CD 개념부터 GitHub Actions 이해하기 - 이론편 (0) | 2025.06.17 |
