기존 CI/CD 문제점
처음 CI/CD 파이프라인을 구성했을 때는 Docker Compose를 사용하지 않고, Redis도 EC2 인스턴스에 직접 설치해서 운영했습니다. Spring Boot 애플리케이션만 Docker로 빌드해 EC2에 배포했고, CI/CD 자체는 동작했지만 인프라 관리 측면에서 아쉬운 점이 있었습니다.
특히 Redis는 컨테이너 외부에 설치되어 있어 관리가 분리되어 있었고, 컨테이너화된 애플리케이션과의 연동이 유연하지 않았습니다. 또한 Redis 데이터의 영속성을 따로 관리하지 않아 재시작 시 데이터가 유지되지 않는 구조도 문제였습니다.
이러한 점들을 보완하기 위해 이번에는 Docker Compose를 도입해 Spring Boot와 Redis를 함께 컨테이너로 실행하고, Redis에는 --appendonly yes와 volume 설정을 적용해 데이터가 유지되도록 구성했습니다. 이 구조를 기반으로 CI/CD 파이프라인을 다시 구축했으며 아래 글은 이 전체 과정을 단계별로 정리한 내용입니다.
CI/CD
CI/CD는 지속적 통합(Continuous Integration)과 지속적 배포(Continuous Deployment)의 줄임말입니다. 코드를 수정하고 GitHub 등에 push하면 자동으로 빌드하고 테스트한 뒤 서버에 배포까지 이어지는 일련의 과정을 자동화해주는 개발 방식입니다.
기존에는 코드를 수정할 때마다 개발자가 직접 서버에 접속해서 빌드하고 배포하는 작업을 반복해야 했습니다. 하지만 CI/CD를 도입하면 이러한 작업을 자동화할 수 있어 작업 속도와 안정성이 크게 향상됩니다.
1. 사전 준비
- AWS EC2 인스턴스 생성 및 접속 가능 상태
- Spring Boot 프로젝트가 GitHub에 업로드된 상태
2. Docker 설치
우선 EC2 인스턴스에 접속하여 Docker를 설치해야 합니다. 아래 명령어를 입력하여 Docker를 설치합니다.
# HTTP 패키지 설치
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common -y
# gpg 키 및 저장소 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository --yes \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
# Docker 엔진 설치
sudo apt-get install docker-ce docker-ce-cli containerd.io -y
위의 명령어로 설치가 완료되면 아래 명령어를 통해 설치를 확인합니다.
# Docker 버전 확인
docker -v
# Docker Compose 버전 확인
docker compose version
3. Dockerfile, docker-compose.yml 파일 생성
(1) Dockerfile

Spring Boot 프로젝트를 빌드하면 build/libs/ 디렉토리에 *.jar 파일이 생성됩니다. 이 JAR 파일을 기반으로 도커 이미지를 만들기 위해 아래와 같이 Dockerfile을 작성합니다.
FROM amazoncorretto:17
WORKDIR /app
COPY ./build/libs/*SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
- amazoncorretto:17: AWS에서 관리하는 OpenJDK 17 이미지입니다.
- WORKDIR /app: 컨테이너 내부 작업 디렉토리 설정
- COPY: 로컬에서 빌드된 JAR 파일을 컨테이너 내부로 복사
- ENTRYPOINT: 컨테이너가 시작되면 JAR 파일을 실행하도록 설정
(2) docker-compose.yml
아래 파일은 Spring Boot 앱과 Redis를 컨테이너로 동시에 띄우는 도커 컴포즈 파일입니다.
services:
app:
image: "${DOCKERHUB_USERNAME}/${PROJECT_NAME}:latest"
ports:
- "${PORT}:${PORT}"
volumes:
- ./application.yml:/app/config/application.yml
environment:
- SPRING_CONFIG_LOCATION=file:/app/config/application.yml
depends_on:
- redis
redis:
image: redis:7
container_name: redis
ports:
- "6379:6379"
restart: always
command: redis-server --appendonly yes
volumes:
- ./redis-data:/data
- image: 빌드된 도커 이미지를 사용합니다.
- ports: 외부와 통신할 수 있도록 포트를 바인딩합니다.
- volumes: EC2에 생성된 application.yml 파일을 컨테이너 내부로 마운트하여 Spring Boot가 외부 설정 파일을 참조할 수 있도록 합니다.
- environment: 컨테이너 실행 시 환경 변수를 설정하는 블록입니다.
- SPRING_CONFIG_LOCATION=file:/app/config/application.yml: Spring Boot가 기본 경로(classpath:/application.yml)가 아닌 외부 마운트된 파일 경로에서 설정 파일을 읽도록 지정합니다.
해당 경로는 위 volumes 설정에서 컨테이너 내부에 연결한 경로와 동일해야 합니다.
- SPRING_CONFIG_LOCATION=file:/app/config/application.yml: Spring Boot가 기본 경로(classpath:/application.yml)가 아닌 외부 마운트된 파일 경로에서 설정 파일을 읽도록 지정합니다.
- depends_on: Redis 컨테이너가 먼저 실행되어야 함을 지정합니다.
- redis
- redis:7: 공식 Redis 이미지 사용
- --appendonly yes: 영속적인 데이터 저장을 위한 설정
- volumes: Redis 데이터를 호스트에 영구 저장
3. GitHub Repository secrets 입력
CI/CD 과정에서 민감한 정보를 외부에 노출하지 않기 위해 GitHub의 Secrets 기능을 활용합니다.
GitHub Repository에서 Settings > Secrets and variables > Actions로 이동한 후 아래 항목들을 하나씩 New repository secret 버튼을 눌러 등록합니다.

| APPLICATION | Spring Boot의 application.yml 전체 내용을 복사하여 입력 |
| DB_URL | 데이터베이스 접속 URL (jdbc:mysql://RDS주소:DB포트/DB명) |
| DB_USERNAME | DB 사용자 이름 |
| DB_PASSWORD | DB 비밀번호 |
| DOCKERHUB_USERNAME | DockerHub 사용자 이름 |
| DOCKERHUB_PASSWORD | DockerHub 비밀번호 |
| EC2_HOST | AWS EC2 인스턴스의 퍼블릭 IPv4 DNS |
| EC2_USERNAME | EC2 접속 계정명 (예: ubuntu) |
| EC2_KEY | EC2 접속용 SSH 개인 키 (.pem 파일을 메모장으로 열어 전부 복사하여 붙여 넣습니다.) |
| PORT | Spring Boot 애플리케이션 실행 포트 (예시: 8080) |
| PROJECT_NAME | 배포할 도커 이미지 이름 |
| REDIS_PORT | Redis 컨테이너 포트 (예시: 6379) |
4.Gradle 파일 생성
이제 GitHub Actions 워크플로우 파일을 생성합니다. GitHub Repository에 진입 후 Actions를 클릭합니다. gradle를 검색하고 Configure를 클릭하여 파일을 생성하면 됩니다.

아래 워크플로우는 GitHub main 브랜치에 push 또는 pull request가 발생했을 때 자동으로 다음 작업을 수행합니다.
- GitHub Actions 환경 구성 (JDK 설치, 빌드 권한 부여 등)
- Spring Boot 애플리케이션 빌드 (Gradle)
- Docker 이미지 생성 및 DockerHub에 업로드
- EC2에 docker-compose.yml, .env, application.yml 복사
- EC2에서 Docker Compose를 실행하여 서비스 재시작
gradle.yml
# Workflow 이름
name: realtimetrip
# 어떤 이벤트가 발생하면 workflow 실행할 지 명시
on:
# main 브랜치에 push나 pull request 발생 시
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
# 위 이벤트 발생 시 실행될 작업들
jobs:
build:
# VM의실행 환경 지정 => 우분투 최신 버전
runs-on: ubuntu-latest
# 실행될 jobs를 순서대로 명시
steps:
- name: Checkout
uses: actions/checkout@v3
# JDK 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
# Gradle Build를 위한 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Gradle Build (test 제외)
- name: Build with Gradle
run: ./gradlew clean build --exclude-task test
# DockerHub 로그인
- name: DockerHub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
# Docker 이미지 빌드
- name: Docker Image Build
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }} .
# DockerHub Push
- name: DockerHub Push
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
# EC2에 docker-compose.yml 파일 복사
- name: Copy docker-compose.yml to EC2
uses: appleboy/scp-action@v0.1.5
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
source: "docker-compose.yml"
target: "~/realtimetrip"
# application.yml 생성 (마운트용)
- name: Create application.yml on EC2
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
script: |
echo "${{ secrets.APPLICATION }}" > ~/realtimetrip/application.yml
# EC2에 .env 파일 생성 (.env는 docker-compose에서 참조하는 환경 변수 파일)
- name: Create .env file on EC2
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
script: |
echo "PORT=${{ secrets.PORT }}" > ~/realtimetrip/.env
echo "DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }}" >> ~/realtimetrip/.env
echo "PROJECT_NAME=${{ secrets.PROJECT_NAME }}" >> ~/realtimetrip/.env
# EC2에서 Docker Compose로 애플리케이션 실행
- name: Application Run
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_KEY }}
script: |
cd ~/realtimetrip
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
5. CI/CD 동작 확인
아래 사진 처럼 workflow가 제대로 동작하면 CI/CD 파이프라인 구축이 완료된 것입니다.

또한, EC2에 접속해서 docker ps 명령어를 입력하면 Spring Boot 애플리케이션과 Redis가 각각 컨테이너 형태로 실행 중인 것을 확인할 수 있습니다.
이번 과정을 통해 CI/CD 파이프라인을 직접 구축해보면서 코드 수정 → 빌드 → 배포까지의 흐름을 자동화할 수 있다는 점이 정말 편리하다는 걸 체감할 수 있었습니다.
또한, Docker Compose를 활용해 Spring Boot 애플리케이션과 Redis를 각각 독립된 컨테이너로 동시에 실행하는 구조를 제대로 이해할 수 있었습니다. 특히 application.yml과 .env 파일을 EC2에 외부 마운트 방식으로 연결해 컨테이너에 주입하는 과정이 처음에는 헷갈렸지만 직접 작성하고 테스트하면서 이해할 수 있었던 것 같습니다.
참고 링크
'CICD' 카테고리의 다른 글
| 도커 개념 정리: 이미지, 컨테이너, 볼륨, 컴포즈 (0) | 2025.06.22 |
|---|