본 프로젝트는 Docker Compose와 GitHub Actions 기반의 CI/CD 구성 예제를 정리한 Repository입니다.
✅ 자동화된 배포 코드 푸시부터 서버 배포까지 자동화
✅ 환경 분리 Local / Dev / Prod 환경의 명확한 분리
✅ 무중단 배포 Nginx 기반 Blue-Green 배포로 서비스 중단 최소화
✅ 보안 관리 Private 서브모듈과 환경변수를 통한 민감 정보 관리
✅ 배포 알림 Discord 웹훅을 통한 개발 현황 및 배포 상태 알림
|
트리거
프로세스 |
트리거
프로세스 |
graph LR
New_Container["새 버전 컨테이너 시작"] --> HealthCheck{"헬스체크<br/>최대 45초"}
HealthCheck -->|성공| Upstream["Nginx upstream 변경"]
Upstream --> Stop_Old["기존 컨테이너 중지"]
HealthCheck -->|실패| Stop_New["새 컨테이너 중지"]
Stop_New --> Keep_Old["기존 컨테이너 유지"]
| 환경 | 용도 | Spring Boot | MySQL | Redis |
|---|---|---|---|---|
| Test | CI 테스트 | GitHub Actions | H2* | Embedded |
| Local | 개발자 로컬 | IDE | Docker | Docker |
| Dev | 개발 서버 | Docker | RDS | Docker |
| Prod | 운영 서버 | Docker | RDS | Docker |
- Private 서브모듈: 민감한 설정 파일 분리 관리
- 환경변수: DB 연결 정보 등 런타임 주입
- Self-hosted Runner: GitHub-hosted Runner 대신 자체 Runner 사용으로 개발/운영 서버 보안그룹을 Runner IP로만 제한
📝 커밋 푸시
📋 PR 생성
✅ TEST.yml 테스트 결과
🚀 CICD.yml 배포 결과 (성공/실패)
# 1. GitHub에서 Private Repository 생성
# 2. 메인 프로젝트에 서브모듈 추가
git submodule add <서브모듈 Repository URL> config# 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# ❌ 하드코딩 금지
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}// Private 서브모듈의 properties 파일을 resources로 복사
tasks.register("copyPrivateProperties", Copy) {
from './config'
include "*.properties"
into 'src/main/resources'
}
// 빌드 시 자동으로 properties 복사
processResources.dependsOn('copyPrivateProperties')# 복사된 properties 파일은 추적하지 않음
src/main/resources/application-*.properties# 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# Private 서브모듈 포함하여 클론
git clone --recurse-submodules <메인 프로젝트 Repository URL>
# 이미 클론한 경우 서브모듈 초기화
git submodule update --init --recursive# 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 리소스는 아키텍처 설계대로 이미 구성되어 있다고 가정합니다.
- 저장소 생성 및 Access Token 발급 필요
- 알림을 받을 서버의 특정 채널 웹훅 URL 필요
- GitHub Actions Runner를 Bastion Host EC2에 설치 및 등록 필요
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 비밀번호
# 각 서버에 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는 수동으로 서버에 생성합니다
┌─────────────┐
│ main │ ← 운영 배포
└──────▲──────┘
│
┌──────┴──────┐
│ develop │ ← 개발 배포
└──────▲──────┘
│
┌───┴────┐
│feature/│ 기능 개발
│ ... │
└────────┘
# feature 브랜치 생성
git checkout -b feature/14-new-feature
# 개발 및 커밋
git add .
git commit -m "feat: 새로운 기능 추가"
# 푸시 → TEST.yml 자동 실행 ✅
git push origin feature/14-new-featuregraph LR
PR["PR 생성"] --> TEST["TEST.yml 자동 실행"]
TEST --> Comment["테스트 결과 코멘트"]
Comment --> Review["코드 리뷰"]
Review --> Merge["Merge"]
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 알림]
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
# Public IP로 직접 접근
http://[DEV_EC2_PUBLIC_IP]:80# ALB DNS로 접근 (Private Subnet 내 EC2)
http://[ALB_DNS_NAME]💡 운영 서버는 Private Subnet에 위치하여 ALB를 통해서만 접근 가능합니다.