Skip to content

Repository files navigation

🚀 Docker Compose 기반 CI/CD 파이프라인

Spring Boot 애플리케이션을 위한 AWS 인프라 구성 및 자동화 배포 시스템

Docker AWS GitHub Actions


📋 목차


🎯 프로젝트 개요

본 프로젝트는 Docker Compose와 GitHub Actions 기반의 CI/CD 구성 예제를 정리한 Repository입니다.

📌 주요 목표

✅ 자동화된 배포          코드 푸시부터 서버 배포까지 자동화
✅ 환경 분리             Local / Dev / Prod 환경의 명확한 분리
✅ 무중단 배포            Nginx 기반 Blue-Green 배포로 서비스 중단 최소화
✅ 보안 관리             Private 서브모듈과 환경변수를 통한 민감 정보 관리
✅ 배포 알림             Discord 웹훅을 통한 개발 현황 및 배포 상태 알림

🛠 기술 스택

Backend

Java Spring Boot Spring Data JPA MySQL Redis

Infrastructure

Docker Docker Compose Nginx

AWS EC2 RDS ALB NAT Gateway

CI/CD

GitHub Actions Bash Script


🏗 아키텍처

AWS 인프라 구조

애플리케이션 구조 (Blue-Green 배포)


✨ 주요 기능

🔄 1. 자동화된 CI/CD 파이프라인

🧪 TEST.yml

트리거

  • 커밋 푸시 (main, develop 제외)
  • PR 생성 (main, develop 대상)

프로세스

1️⃣ 코드 체크아웃 및 의존성 캐싱
2️⃣ H2 + Redis 기반 테스트 실행
3️⃣ 테스트 결과 리포트 생성
4️⃣ PR에 테스트 결과 자동 코멘트

🚀 CICD.yml

트리거

  • develop/main 브랜치 머지

프로세스

1️⃣ Gradle 빌드 및 Docker 이미지 생성
2️⃣ DockerHub에 이미지 푸시
3️⃣ Self-hosted Runner에서 배포 실행
4️⃣ Blue-Green 무중단 배포

🔵🟢 2. Blue-Green 무중단 배포

graph LR
    New_Container["새 버전 컨테이너 시작"] --> HealthCheck{"헬스체크<br/>최대 45초"}
    HealthCheck -->|성공| Upstream["Nginx upstream 변경"]
    Upstream --> Stop_Old["기존 컨테이너 중지"]
    HealthCheck -->|실패| Stop_New["새 컨테이너 중지"]
    Stop_New --> Keep_Old["기존 컨테이너 유지"]
Loading

📦 3. 환경별 설정 관리

환경 용도 Spring Boot MySQL Redis
Test CI 테스트 GitHub Actions H2* Embedded
Local 개발자 로컬 IDE Docker Docker
Dev 개발 서버 Docker RDS Docker
Prod 운영 서버 Docker RDS Docker

🔒 4. 보안 강화

  • Private 서브모듈: 민감한 설정 파일 분리 관리
  • 환경변수: DB 연결 정보 등 런타임 주입
  • Self-hosted Runner: GitHub-hosted Runner 대신 자체 Runner 사용으로 개발/운영 서버 보안그룹을 Runner IP로만 제한

🔔 5. 실시간 알림 (Discord 웹훅)

📝 커밋 푸시
📋 PR 생성
✅ TEST.yml 테스트 결과
🚀 CICD.yml 배포 결과 (성공/실패)

📌 로컬 개발 준비

1️⃣ Private 서브모듈 설정

1) 서브모듈 추가

# 1. GitHub에서 Private Repository 생성

# 2. 메인 프로젝트에 서브모듈 추가
git submodule add <서브모듈 Repository URL> config

2) 각 환경별 Properties 작성

# config 디렉토리에 properties 파일 생성
cd config
touch application-test.properties
touch application-local.properties
touch application-dev.properties
touch application-prod.properties
# MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/database?characterEncoding=UTF-8&serverTimezone=Asia/Seoul&useSSL=false
spring.datasource.username=사용자명
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.default_batch_fetch_size=100
spring.jpa.show-sql=true

# Redis
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.password=비밀번호

# Logging
logging.level.root=INFO
logging.level.your.package.name=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql=TRACE

# Actuator
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

⚠️ Dev/Prod 환경은 환경변수 사용 필수

# ❌ 하드코딩 금지
spring.datasource.url=jdbc:mysql://prod-rds:3306/database
spring.datasource.password=actual_password
spring.data.redis.password=actual_redis_password

# ✅ 플레이스홀더 사용
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.data.redis.password=${REDIS_PASSWORD}

3) build.gradle 설정

// Private 서브모듈의 properties 파일을 resources로 복사
tasks.register("copyPrivateProperties", Copy) {
    from './config'
    include "*.properties"
    into 'src/main/resources'
}

// 빌드 시 자동으로 properties 복사
processResources.dependsOn('copyPrivateProperties')

4) .gitignore 설정

# 복사된 properties 파일은 추적하지 않음
src/main/resources/application-*.properties

5) 커밋 및 푸시

# 1. 커밋
git add .
git commit -m "Initial properties files"
git push origin main

# 2. 메인 프로젝트로 돌아와서 서브모듈 변경사항 커밋
cd ..
git add .gitmodules config
git commit -m "Add config submodule"
git push

2️⃣ 저장소 클론

# Private 서브모듈 포함하여 클론
git clone --recurse-submodules <메인 프로젝트 Repository URL>

# 이미 클론한 경우 서브모듈 초기화
git submodule update --init --recursive

3️⃣ 로컬 개발 환경 설정

# 1. Docker Compose로 MySQL + Redis 실행
docker compose up -d

# 2. 컨테이너 상태 확인
docker compose ps

애플리케이션 실행

방법 1: IDE (ex.IntelliJ IDEA)

1. Run/Debug Configurations 열기
2. Active profiles: local 입력
3. Run 버튼 클릭

방법 2: Gradle 명령어

./gradlew bootRun --args='--spring.profiles.active=local'

🚀 배포 준비

💡 VPC, EC2, RDS, ALB 등 주요 AWS 리소스는 아키텍처 설계대로 이미 구성되어 있다고 가정합니다.

1️⃣ DockerHub 설정

  • 저장소 생성 및 Access Token 발급 필요

2️⃣ Discord 웹훅 설정

  • 알림을 받을 서버의 특정 채널 웹훅 URL 필요

3️⃣ Self-hosted Runner 설정

  • GitHub Actions Runner를 Bastion Host EC2에 설치 및 등록 필요

4️⃣ GitHub Secrets 설정

Repository → Settings → Secrets and variables → Actions

🔐 Private 서브모듈
PERSONAL_ACCESS_TOKEN: GitHub PAT
🐳 Docker Hub
DOCKER_ID: DockerHub 사용자명
DOCKER_PASSWORD: DockerHub 액세스 토큰
DOCKER_REPOSITORY: DockerHub 저장소명
💬 Discord 웹훅
DISCORD_DEV_WEBHOOK: 개발 채널 웹훅 URL
DISCORD_PROD_WEBHOOK: 운영 채널 웹훅 URL
🔧 개발 서버 (Dev)
EC2_DEV_HOST: 개발 서버 EC2 Private IP
EC2_DEV_PEM_KEY: 개발 서버 SSH Private Key (전체 내용)
DB_DEV_URL: jdbc:mysql://dev-rds-endpoint:3306/database?useSSL=true&serverTimezone=Asia/Seoul
DB_DEV_USERNAME: RDS 사용자명
DB_DEV_PASSWORD: RDS 비밀번호
REDIS_DEV_PASSWORD: Redis 비밀번호
🚀 운영 서버 (Prod)
EC2_PROD_HOST: 운영 서버 EC2 Private IP
EC2_PROD_PEM_KEY: 운영 서버 SSH Private Key (전체 내용)
DB_PROD_URL: jdbc:mysql://prod-rds-endpoint:3306/database?useSSL=true&serverTimezone=Asia/Seoul
DB_PROD_USERNAME: RDS 사용자명
DB_PROD_PASSWORD: RDS 비밀번호
REDIS_PROD_PASSWORD: Redis 비밀번호

5️⃣ Nginx 설정 (개발/운영 서버)

# 각 서버에 Nginx 디렉토리 생성 & 설정 파일 생성
mkdir -p /home/ubuntu/nginx
vi /home/ubuntu/nginx/nginx.conf

⚠️ 수동 배치 이유

  • docker-compose.infra.yml에서 nginx.conf를 볼륨으로 마운트: /home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf
  • CICD.yml에서 매번 덮어쓰면 현재 활성화된 컨테이너(Blue/Green) 정보가 초기화되어 배포가 실패합니다
  • 따라서 nginx.conf는 수동으로 서버에 생성합니다

🔄 CI/CD 파이프라인

📊 브랜치 전략 (Git Flow)

┌─────────────┐
│    main     │  ← 운영 배포
└──────▲──────┘
       │
┌──────┴──────┐
│   develop   │  ← 개발 배포
└──────▲──────┘
       │
   ┌───┴────┐
   │feature/│     기능 개발
   │  ...   │     
   └────────┘

🔀 워크플로우

1️⃣ 기능 개발

# feature 브랜치 생성
git checkout -b feature/14-new-feature

# 개발 및 커밋
git add .
git commit -m "feat: 새로운 기능 추가"

# 푸시 → TEST.yml 자동 실행 ✅
git push origin feature/14-new-feature

2️⃣ Pull Request

graph LR
    PR["PR 생성"] --> TEST["TEST.yml 자동 실행"]
    TEST --> Comment["테스트 결과 코멘트"]
    Comment --> Review["코드 리뷰"]
    Review --> Merge["Merge"]
Loading

3️⃣ 개발/운영 서버 배포

graph LR
    Dev[develop merge] --> CICD[CICD.yml 자동 실행]
    Prod[main merge] --> CICD

    CICD --> Build[Docker 이미지 빌드]
    Build --> Push[DockerHub 푸시]
    Push --> Deploy[해당 EC2 배포]
    Deploy --> BG[Blue-Green 전환]
    BG --> Notify[Discord 알림]
Loading

📁 프로젝트 구조

docker-compose-deploy/
├── .github/
│   └── workflows/
│       ├── TEST.yml                     # 테스트 파이프라인
│       └── CICD.yml                     # 빌드 & 배포 파이프라인
├── deploy/
│   ├── docker-compose.infra.yml        # Nginx + Redis
│   ├── docker-compose.server.yml       # Spring Boot (Blue/Green)
│   └── server-deploy.sh                # 배포 스크립트
├── nginx/
│   └── nginx.conf                       # Nginx 설정 파일
├── config/                              # Private 서브모듈
│   ├── application-test.properties
│   ├── application-local.properties
│   ├── application-dev.properties
│   └── application-prod.properties
├── src/
│   ├── main/
│   │   ├── java/
│   │   └── resources/
│   └── test/
├── docker-compose.yml                   # 로컬 개발용 (MySQL + Redis)
├── Dockerfile
├── build.gradle
├── .gitignore
├── .gitmodules
└── README.md

🌐 서버 접근 방법

개발 서버 (Dev)

# Public IP로 직접 접근
http://[DEV_EC2_PUBLIC_IP]:80

운영 서버 (Prod)

# ALB DNS로 접근 (Private Subnet 내 EC2)
http://[ALB_DNS_NAME]

💡 운영 서버는 Private Subnet에 위치하여 ALB를 통해서만 접근 가능합니다.

Releases

Packages

Used by

Contributors

Languages