오늘날 소프트웨어 개발에서 배포는 단순한 마무리 단계가 아니라, 전체 개발 라이프사이클을 최적화하는 핵심 요소로 자리잡고 있습니다. 특히 Spring Boot 기반 애플리케이션을 어떻게 효율적으로 패키징하고 자동으로 배포할 수 있을지 고민하는 개발자라면, Docker Compose와 CI/CD 파이프라인을 활용한 자동화 전략은 더 이상 선택이 아닌 필수입니다.

📋 목차
- 1. 도입: 자동화 배포, 왜 지금 필요한가?
- 2. Dockerizing Spring Boot 애플리케이션
- 3. Docker Compose로 다중 서비스 통합
- 4. 개발용 vs 운영용 Compose 설정 분리 전략
- 5. CI/CD 파이프라인 개요: 자동화의 시작점
- 6. GitHub Actions를 활용한 자동 빌드 & 배포
- 7. AWS EC2 또는 클라우드 서버에 자동 배포
- 8. 실전 예제: Todo 애플리케이션 자동 배포
- 9. 유지보수와 모니터링 전략
- 10. 결론: 자동화 배포로 얻는 실질적 이득
1. 도입: 자동화 배포, 왜 지금 필요한가?
한때 개발자들은 코드를 로컬에서 테스트한 뒤, 수동으로 서버에 올려야만 했습니다. 이는 사람의 손이 닿는 과정이 많을수록 실수가 발생하고, 배포 환경과 개발 환경 간의 차이로 인한 예기치 않은 오류들이 빈번히 발생하게 만들었습니다. 그러나 현대의 소프트웨어 개발은 속도와 안정성을 동시에 요구합니다. 바로 이 지점에서 자동화 배포가 큰 역할을 합니다.
자동화 배포란 애플리케이션이 빌드되고, 테스트되며, 운영 서버에 반영되는 전 과정을 스크립트나 설정 파일로 정의하고 자동으로 실행되도록 만드는 것을 의미합니다. 이를 통해 배포의 반복성과 예측 가능성을 보장하며, 인간의 실수를 최소화할 수 있습니다.
특히 Spring Boot와 같은 Java 기반 프레임워크는 구조가 명확하고 설정이 자유로워 컨테이너 환경과 잘 어울립니다. 여기에 Docker를 활용해 이미지를 빌드하고, Docker Compose를 통해 다양한 서비스를 함께 구성하면, 단일 명령어로 복잡한 애플리케이션 아키텍처를 실행할 수 있습니다. 여기에 CI/CD 툴까지 더하면 코드를 푸시하는 즉시 테스트 및 배포가 완료되는 자동화 생태계가 완성됩니다.
이번 시리즈에서는 Spring Boot 애플리케이션을 Docker로 감싸는 것부터 Docker Compose 설정, 그리고 GitHub Actions 기반의 자동화 파이프라인 구축에 이르기까지, **배포 자동화 전 과정을 체계적으로** 다룰 것입니다. 클라우드 서버까지 실제 배포가 완료되는 전체 흐름을 실전 예제를 통해 확인해보세요.
이 글을 끝까지 따라간다면, 단순한 개발자가 아니라 ‘배포까지 책임지는 엔지니어’로 한 걸음 나아가게 될 것입니다.
2. Dockerizing Spring Boot 애플리케이션
애플리케이션을 컨테이너 환경에서 구동하기 위한 첫 단계는, 소스 코드를 Docker 이미지로 변환하는 작업입니다. 이 과정을 흔히 Dockerizing이라 부르며, Spring Boot는 자체적으로 내장 톰캣을 포함하고 있기 때문에 Dockerizing이 특히 수월한 편입니다.
아래는 기본적인 Spring Boot 프로젝트를 Docker 이미지로 만드는 전체 흐름입니다:
- ① 애플리케이션 빌드 (JAR 파일 생성)
- ②
Dockerfile
생성 - ③ Docker 이미지 빌드
- ④ 컨테이너 실행 및 테스트
Step 1. Spring Boot 프로젝트 준비 및 빌드
Spring Initializr를 활용해 간단한 웹 애플리케이션을 구성하고, 아래 명령어로 JAR 파일을 생성합니다.
./gradlew clean build
빌드가 완료되면 build/libs/
디렉토리에 JAR 파일이 생성됩니다.
Step 2. Dockerfile 작성
Dockerfile은 애플리케이션을 어떤 환경에서 어떻게 실행할지를 정의하는 스크립트입니다. 다음은 가장 기본적인 Spring Boot Dockerfile 예시입니다:
FROM openjdk:17-jdk-slim
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
위 Dockerfile은 OpenJDK 기반의 경량 이미지 위에 애플리케이션 JAR 파일을 복사한 후, 실행 엔트리를 지정합니다.
Step 3. Docker 이미지 빌드
Dockerfile이 있는 루트 디렉토리에서 아래 명령어를 실행해 이미지를 생성합니다. 태그는 프로젝트 이름이나 버전으로 지정하면 관리하기 쉽습니다.
docker build -t my-springboot-app .
Step 4. Docker 컨테이너 실행
이제 해당 이미지를 기반으로 컨테이너를 실행해봅니다.
docker run -d -p 8080:8080 --name spring-app my-springboot-app
정상적으로 실행되면 http://localhost:8080
에서 애플리케이션을 확인할 수 있습니다.
💡 참고: 멀티 스테이지 빌드로 이미지 최적화
프로덕션 환경에서는 이미지 크기를 줄이고, 불필요한 파일을 제거하는 것이 중요합니다. 이를 위해 멀티 스테이지 빌드를 적용해볼 수 있습니다:
FROM gradle:7.6-jdk17 AS build
COPY --chown=gradle:gradle . /home/gradle/project
WORKDIR /home/gradle/project
RUN gradle build --no-daemon
FROM openjdk:17-jdk-slim
COPY --from=build /home/gradle/project/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
이 방식은 gradle
이미지에서 빌드하고, 실제 런타임 환경에는 필요한 JAR 파일만을 복사하기 때문에 보안성과 성능 면에서 훨씬 유리합니다.
이제 Docker 이미지화가 완료되었으니, 다음 단계에서는 여러 개의 서비스를 통합 실행할 수 있는 Docker Compose 설정으로 넘어가 보겠습니다.
3. Docker Compose로 다중 서비스 통합
단일 Spring Boot 애플리케이션만 실행하는 경우에는 docker run
명령어만으로도 충분하지만, 실무에서는 하나의 애플리케이션이 데이터베이스, 캐시 서버, 메시지 브로커, 프론트엔드 등 여러 컴포넌트와 함께 구성되는 것이 일반적입니다. 이러한 여러 컨테이너를 효율적으로 정의하고 실행하기 위해 사용되는 도구가 바로 Docker Compose입니다.
Docker Compose는 YAML 형식의 설정 파일을 기반으로 여러 개의 컨테이너를 하나의 서비스 단위로 정의하고, docker-compose up
명령어 하나로 전체 스택을 통합 실행할 수 있도록 해줍니다.
Docker Compose의 핵심 요소
Docker Compose의 설정 파일인 docker-compose.yml
은 다음과 같은 구조를 갖습니다:
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/appdb
SPRING_DATASOURCE_USERNAME: user
SPRING_DATASOURCE_PASSWORD: pass
db:
image: mysql:8
restart: always
environment:
MYSQL_DATABASE: appdb
MYSQL_USER: user
MYSQL_PASSWORD: pass
MYSQL_ROOT_PASSWORD: rootpass
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
이 설정에서 app
은 앞서 Dockerizing한 Spring Boot 애플리케이션이고, db
는 MySQL 데이터베이스입니다. depends_on
키워드를 통해 의존 순서를 지정할 수 있으며, 환경변수를 통해 컨테이너 간 통신 주소를 설정합니다.
서비스 이름이 곧 호스트 이름
Docker Compose는 각 서비스를 내부 네트워크에서 자동으로 DNS 등록해주기 때문에, DB 컨테이너의 이름이 곧 Spring Boot에서 접근 가능한 호스트 이름이 됩니다. 예를 들어 위 예제에서 DB는 db:3306
주소로 접근 가능합니다.
Redis, MongoDB, RabbitMQ 추가 예시
다른 컴포넌트를 추가할 때도 동일한 방식으로 services 항목을 확장할 수 있습니다. 예를 들어 Redis를 추가하고 싶다면 다음과 같이 구성할 수 있습니다:
redis:
image: redis:6
ports:
- "6379:6379"
Spring Boot에서 Redis를 사용하도록 구성하면 애플리케이션은 redis:6379
주소를 통해 해당 컨테이너와 통신하게 됩니다.
Compose로 전체 스택 실행
설정이 완료되었다면 다음 명령어 하나로 전체 컨테이너 환경을 실행할 수 있습니다:
docker-compose up -d
이 명령어는 docker-compose.yml
에 정의된 모든 서비스를 백그라운드에서 실행합니다. 실행 중인 컨테이너 목록은 docker ps
명령어로 확인할 수 있습니다.
실전 팁: 볼륨과 환경 변수의 활용
볼륨(volumes
)을 사용하면 DB 데이터를 컨테이너 외부에 저장하여 재시작에도 데이터가 보존됩니다. 또한, 환경 변수는 .env
파일로 분리하여 민감한 정보를 안전하게 관리할 수 있습니다. 이는 다음 단락에서 더욱 자세히 다루겠습니다.
이제 여러 개의 서비스를 하나의 파일에서 정의하고 실행할 수 있게 되었으므로, 다음으로는 개발 환경과 운영 환경을 어떻게 나눠서 관리할 수 있을지에 대해 살펴보겠습니다.
4. 개발용 vs 운영용 Compose 설정 분리 전략
Docker Compose를 활용해 여러 서비스를 손쉽게 실행할 수 있지만, 개발 환경과 운영 환경에서는 요구 사항이 다릅니다. 예를 들어 개발 환경에서는 디버깅을 위해 로그를 자세히 출력하거나, 로컬 소스 코드를 마운트하여 실시간 반영되도록 설정하는 반면, 운영 환경에서는 보안, 성능, 리소스 효율을 최우선으로 고려해야 합니다.
이러한 차이를 고려하여, Docker Compose 설정을 분리하고 계층화하는 전략은 실무에서 반드시 필요한 작업입니다. 이를 위해 Docker Compose는 다음과 같은 기능을 제공합니다:
override
파일로 설정 계층화.env
파일로 공통 환경 변수 분리- 프로파일(profile)로 특정 서비스 조건부 실행
방법 1. docker-compose.override.yml
사용
Docker Compose는 docker-compose.yml
외에, 같은 경로에 위치한 docker-compose.override.yml
파일을 자동으로 함께 병합합니다. 개발 환경에서는 이 파일에 개발 전용 설정을 분리해 둘 수 있습니다.
예를 들어, docker-compose.override.yml
파일에 소스 코드 볼륨 마운트와 디버깅 포트를 추가할 수 있습니다:
services:
app:
volumes:
- ./src:/app/src
environment:
SPRING_PROFILES_ACTIVE: dev
ports:
- "5005:5005"
이렇게 하면 개발자는 코드 수정 시마다 컨테이너를 재시작하지 않아도 되고, IDE에서 원격 디버깅도 가능합니다.
방법 2. .env
파일을 통한 변수 관리
.env
파일은 공통 환경 변수(포트, DB 비밀번호 등)를 정의하여 YAML 파일에 반복 사용을 피하고, 민감 정보를 소스 코드 외부에 분리하는 데 유용합니다. 예:
SPRING_DATASOURCE_USERNAME=appuser
SPRING_DATASOURCE_PASSWORD=secret
APP_PORT=8080
그리고 docker-compose.yml
에는 다음처럼 참조할 수 있습니다:
services:
app:
ports:
- "${APP_PORT}:8080"
environment:
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
이렇게 하면 환경 별로 .env
파일만 교체하거나 Git 관리에서 제외하여 보안도 확보할 수 있습니다.
방법 3. Profile을 활용한 조건부 서비스 실행
Docker Compose 3.9부터는 profile 기능이 추가되어 특정 서비스만 선택적으로 실행할 수 있습니다. 운영 환경에서만 필요한 로깅, 모니터링, 백업 등의 부가 서비스를 관리할 때 유용합니다.
services:
prometheus:
image: prom/prometheus
profiles:
- monitoring
그리고 다음과 같이 실행하면 해당 프로파일에 속한 서비스만 실행됩니다:
docker-compose --profile monitoring up
요약: 설정 분리의 목적은 명확성, 보안성, 유지보수성
Docker Compose 설정을 개발용과 운영용으로 분리하면 다음과 같은 이점이 있습니다:
- 환경별 설정을 손쉽게 스위칭할 수 있음
- 코드 반영 속도 개선 및 디버깅 용이
- 운영 환경에서 불필요한 리소스 낭비 방지
이제 개발자에게 필요한 로컬 개발 환경과, 운영 서버에 배포되는 환경을 각각 최적화하여 관리할 수 있게 되었습니다. 다음으로는, 이렇게 잘 구성된 Compose 기반 애플리케이션을 자동으로 배포하는 CI/CD 파이프라인의 개요로 넘어가 보겠습니다.
5. CI/CD 파이프라인 개요: 자동화의 시작점
지금까지 Docker와 Docker Compose를 활용해 Spring Boot 애플리케이션을 효과적으로 패키징하고 통합 실행하는 방법을 살펴보았습니다. 그러나 이 모든 과정이 수동으로 반복되어야 한다면, 다시 오류와 비효율의 늪에 빠지게 됩니다. 여기서 우리가 집중해야 할 키워드는 바로 CI/CD (지속적 통합/지속적 배포)입니다.
CI/CD는 개발자의 코드가 저장소에 병합되는 순간부터 테스트, 빌드, 배포까지의 모든 과정을 자동화하여 더 빠르고 안정적인 서비스 제공을 가능하게 합니다. 특히 컨테이너 기반의 마이크로서비스 구조에서는 이 자동화 체계가 필수적입니다.
CI (Continuous Integration)란?
지속적 통합(Continuous Integration)은 개발자가 코드를 변경할 때마다, 자동으로 코드를 병합하고, 테스트 및 빌드 과정을 실행하는 프로세스를 의미합니다. 주요 목표는 다음과 같습니다:
- 코드 충돌 사전 예방
- 버그 조기 발견
- 일관된 빌드 품질 유지
Spring Boot에서는 Gradle 또는 Maven 기반으로 테스트 및 빌드를 쉽게 자동화할 수 있으며, 이를 CI 파이프라인에 통합하면 코드 변경 시마다 JAR 파일 생성까지 자동화할 수 있습니다.
CD (Continuous Delivery/Deployment)란?
CI 이후 단계인 CD는 두 가지 의미로 나뉩니다:
- Continuous Delivery: 코드가 빌드된 후 운영 서버에 수동 승인 후 배포 가능한 상태까지 자동화
- Continuous Deployment: 승인 없이 완전 자동으로 운영 배포까지 완료
팀의 규모, 조직의 규칙, 제품의 민감도에 따라 어느 수준의 자동화를 선택할지 달라질 수 있으며, 보통은 테스트 환경까지 자동 배포하고 운영은 수동 승인을 거치는 패턴이 많습니다.
CI/CD 도구 비교
도구 | 특징 | 적합 환경 |
---|---|---|
GitHub Actions | GitHub 저장소 기반의 간결하고 직관적인 구성 | 개인/소규모 팀, 오픈소스 프로젝트 |
GitLab CI/CD | GitLab과 완전 통합된 유연한 파이프라인 | 엔터프라이즈 규모, 자체 GitLab 서버 운영 시 |
Jenkins | 높은 확장성과 플러그인 생태계 보유 | 복잡한 커스터마이징 및 사내 구축 필요 시 |
Spring Boot + Docker에 적합한 CI/CD 전략
Spring Boot는 Gradle 빌드를 통해 JAR 파일을 만들 수 있고, Dockerfile로 손쉽게 이미지화가 가능하며, Docker Compose로 여러 서비스를 함께 실행할 수 있습니다. 이 구조는 **CI/CD 파이프라인 구축에 매우 유리**합니다. 예를 들어:
- GitHub Actions로 코드 변경 시 자동 빌드 및 Docker 이미지 생성
- Docker Hub 또는 GitHub Container Registry에 이미지 푸시
- 원격 서버에서 자동으로 Docker Compose를 통해 최신 버전 재배포
이러한 흐름을 기반으로 하면, 개발자는 ‘코드를 푸시하는 것만으로’ 새로운 버전의 애플리케이션이 운영 서버에 자동 반영되도록 만들 수 있습니다.
다음 단락에서는 이 흐름을 실제로 구현하기 위해 GitHub Actions를 활용한 빌드 및 배포 자동화 구성을 시작하겠습니다.
6. GitHub Actions를 활용한 자동 빌드 & 배포
GitHub Actions는 GitHub에서 제공하는 CI/CD 플랫폼으로, 코드 저장소 내에서 손쉽게 파이프라인을 구축하고 자동화할 수 있게 해줍니다. 특히 Spring Boot + Docker 프로젝트와의 궁합이 매우 좋아, 복잡한 외부 인프라 없이도 빌드부터 배포까지 전 과정 자동화가 가능합니다.
이번 섹션에서는 GitHub Actions를 이용해 다음과 같은 파이프라인을 구축하는 방법을 소개합니다:
- Spring Boot 프로젝트를 빌드하여 JAR 생성
- Docker 이미지 생성 및 레지스트리에 푸시
- SSH를 통해 원격 서버에 접속하여 Docker Compose로 배포
워크플로우 디렉토리 및 파일 생성
GitHub Actions 설정은 프로젝트 루트에 .github/workflows
디렉토리를 생성하고, 그 안에 YAML 파일을 정의하는 방식으로 진행됩니다.
mkdir -p .github/workflows
touch .github/workflows/deploy.yml
기본 워크플로우 구성 예시
다음은 GitHub Actions의 워크플로우 예시입니다. 이 구성은 푸시 시 자동으로 실행되며, Docker 이미지를 빌드하고 서버에 배포합니다.
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew build
- name: Build Docker image
run: docker build -t ${{ secrets.DOCKER_IMAGE_NAME }} .
- name: Push to Docker Hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker push ${{ secrets.DOCKER_IMAGE_NAME }}
- name: Deploy to remote server
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.REMOTE_KEY }}
script: |
cd /home/ubuntu/myapp
docker pull ${{ secrets.DOCKER_IMAGE_NAME }}
docker-compose down
docker-compose up -d
Secrets 등록 방법
위 YAML에서 사용된 모든 민감 정보(DOCKER_USERNAME
, DOCKER_PASSWORD
, REMOTE_HOST
등)는 GitHub 저장소의 Settings → Secrets에서 등록해야 합니다. 이를 통해 워크플로우 내 민감 정보 노출 없이 안전하게 파이프라인을 구성할 수 있습니다.
실행 확인 및 로그 확인
코드를 main 브랜치에 푸시하면 GitHub Actions가 자동으로 트리거되고, 해당 작업의 로그와 성공 여부는 Actions 탭에서 실시간으로 확인할 수 있습니다. 빌드 실패나 배포 실패 시 원인을 빠르게 파악할 수 있어, 안정적인 운영에 큰 도움이 됩니다.
💡 팁: 멀티 브랜치 환경에서 환경 분리
브랜치 조건을 추가하여 main
은 운영 서버, develop
은 스테이징 서버에 배포되도록 구성할 수 있습니다. 예:
on:
push:
branches:
- main
- develop
이처럼 GitHub Actions를 활용하면, 코드 변경만으로 자동 빌드 및 배포가 가능해져 개발 생산성과 운영 안정성을 동시에 확보할 수 있습니다.
이제 애플리케이션은 완전히 자동화된 배포 파이프라인에 탑승했습니다. 다음 단계는 이 파이프라인의 결과물을 실제 AWS EC2 또는 클라우드 서버에 어떻게 안정적으로 반영할 것인가에 대한 구체적 설명입니다.
7. AWS EC2 또는 클라우드 서버에 자동 배포
CI/CD 파이프라인에서 가장 핵심적이면서도 민감한 단계는 바로 원격 서버 배포입니다. 이 단계에서 코드는 운영 서버로 전송되고, Docker Compose를 통해 최신 버전이 기동됩니다. 이번 단락에서는 AWS EC2를 예시로 하여, 원격 서버에 Spring Boot 애플리케이션을 자동으로 배포하는 과정을 소개합니다.
1. AWS EC2 서버 준비
AWS EC2 인스턴스를 생성하고, 퍼블릭 IP 또는 고정 도메인을 설정합니다. EC2에 SSH 접속이 가능하도록 보안 그룹에서 포트 22을 열고, 개인 키 (.pem)를 안전하게 보관해야 합니다.
EC2 인스턴스 접속 테스트:
ssh -i "my-key.pem" ubuntu@
2. Docker 환경 및 프로젝트 디렉토리 구성
EC2에 Docker 및 Docker Compose를 설치합니다:
sudo apt update
sudo apt install docker.io docker-compose -y
그 후, 프로젝트 루트 디렉토리(ex. /home/ubuntu/myapp
)를 생성하고 다음 파일을 배치합니다:
docker-compose.yml
.env
파일 (운영용 설정)- Nginx 설정 파일(optional)
3. SSH 비밀 키 GitHub Secrets에 등록
GitHub Actions에서 EC2 서버에 접속하려면, 개인 키를 GitHub의 Secrets에 등록해야 합니다. 다음 명령으로 PEM 키를 Base64 인코딩한 후 등록합니다:
cat my-key.pem | base64
출력된 문자열을 REMOTE_KEY
라는 이름으로 GitHub Settings → Secrets에 등록합니다. 이 외에도 아래와 같은 항목이 필요합니다:
Secrets 이름 | 설명 |
---|---|
REMOTE_HOST | EC2 퍼블릭 IP 또는 도메인 |
REMOTE_USER | 기본 접속 계정 (예: ubuntu) |
REMOTE_KEY | Base64 인코딩된 PEM 키 |
4. GitHub Actions에서 원격 서버로 배포 자동화
앞서 정의한 deploy.yml
의 appleboy/ssh-action
모듈을 사용하여, EC2에 배포 명령어를 실행합니다:
- name: Deploy to remote server
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.REMOTE_KEY }}
script: |
cd /home/ubuntu/myapp
docker pull ${{ secrets.DOCKER_IMAGE_NAME }}
docker-compose down
docker-compose up -d
이 스크립트는 다음의 동작을 수행합니다:
- 지정된 디렉토리로 이동
- 최신 Docker 이미지를 레지스트리에서 가져옴
- 기존 컨테이너 종료 및 제거
- 새로운 Compose 설정으로 애플리케이션 재시작
5. 실시간 배포 확인 및 문제 해결
배포가 완료되면 docker ps
명령어로 정상 실행 여부를 확인할 수 있습니다.
애플리케이션 상태나 로그는 다음 명령어로 모니터링할 수 있습니다:
docker-compose logs -f
docker logs [컨테이너ID]
요약: 코드 커밋 → 클라우드 서버 자동 배포까지
이제 GitHub Actions와 Docker Compose를 연동한 자동화 파이프라인은 다음과 같은 흐름을 실현합니다:
- 코드 변경사항 푸시
- GitHub Actions 자동 빌드 및 Docker 이미지 생성
- Docker Hub 업로드
- EC2 접속 → 최신 이미지 Pull → 컨테이너 교체
모든 과정이 자동화되어, 사람이 개입하지 않아도 안정적인 배포가 이루어지는 시스템을 갖춘 셈입니다. 이제 실제로 간단한 Spring Boot 애플리케이션을 바탕으로 전 과정을 통합해보는 실전 예제를 살펴보겠습니다.
8. 실전 예제: Todo 애플리케이션 자동 배포
이제 이론을 넘어 실제로 작동하는 자동화 배포 환경을 구축해보겠습니다. 예제로는 간단한 Spring Boot 기반의 Todo 리스트 애플리케이션을 사용하며, 앞서 설명한 모든 자동화 단계를 직접 연결해볼 것입니다.
프로젝트 개요
- Backend: Spring Boot + JPA + MySQL
- Database: MySQL (Docker로 실행)
- Build: Gradle
- CI/CD: GitHub Actions
- 배포: AWS EC2 + Docker Compose
1. Spring Boot Todo 애플리케이션 구성
REST API 기반으로 간단한 Todo 리스트를 등록/조회/삭제할 수 있는 애플리케이션입니다. 주요 클래스는 다음과 같습니다:
@Entity
public class Todo {
@Id @GeneratedValue
private Long id;
private String title;
private boolean completed;
}
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoRepository todoRepository;
public TodoController(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@GetMapping
public List<Todo> getTodos() {
return todoRepository.findAll();
}
@PostMapping
public Todo createTodo(@RequestBody Todo todo) {
return todoRepository.save(todo);
}
}
2. Dockerfile 작성
이전 단락에서 설명한 멀티 스테이지 빌드를 적용해 효율적인 Dockerfile을 구성합니다.
FROM gradle:7.6-jdk17 AS builder
COPY --chown=gradle:gradle . /home/gradle/project
WORKDIR /home/gradle/project
RUN gradle build --no-daemon
FROM openjdk:17-jdk-slim
COPY --from=builder /home/gradle/project/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
3. docker-compose.yml 구성
Spring Boot 애플리케이션과 MySQL을 함께 실행하도록 Docker Compose 설정을 구성합니다.
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/tododb
SPRING_DATASOURCE_USERNAME: todo_user
SPRING_DATASOURCE_PASSWORD: secret
depends_on:
- db
db:
image: mysql:8
restart: always
environment:
MYSQL_DATABASE: tododb
MYSQL_USER: todo_user
MYSQL_PASSWORD: secret
MYSQL_ROOT_PASSWORD: rootpass
ports:
- "3306:3306"
4. GitHub Actions 워크플로우 구성
자동화된 CI/CD 파이프라인을 통해 코드 변경 시 자동 빌드 및 배포가 되도록 설정합니다. deploy.yml
파일에는 앞서 다룬 GitHub Actions 설정을 그대로 반영합니다. 핵심 단계는 다음과 같습니다:
- 코드 체크아웃
- Gradle 빌드 및 JAR 생성
- Docker 이미지 빌드 및 Docker Hub 푸시
- EC2 SSH 접속 후 Docker Compose로 배포
실제 워크플로우 예시는 앞서 6장에서 제시한 내용을 그대로 사용할 수 있습니다.
5. EC2에서 Compose 실행 및 테스트
GitHub Actions가 성공적으로 완료되면, EC2에서 애플리케이션이 배포된 상태를 확인할 수 있습니다.
http://
– Todo 목록 조회:8080/api/todos POST
로 새로운 Todo 항목 생성
Postman이나 curl을 사용해 다음과 같이 API를 호출해 볼 수 있습니다:
curl -X POST http://:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title":"자동 배포 성공", "completed":false}'
요약
이번 실전 예제를 통해 다음과 같은 전 과정을 구현했습니다:
- Spring Boot Todo 애플리케이션 구현
- Dockerfile 및 Docker Compose 설정
- GitHub Actions를 통한 자동 빌드
- Docker Hub 푸시 및 EC2 자동 배포
이제 하나의 코드 커밋만으로 서버에 자동으로 배포되는 자동화된 인프라가 구축되었습니다. 다음 단락에서는 서비스 운영 이후의 유지보수와 모니터링 전략에 대해 알아보겠습니다.
9. 유지보수와 모니터링 전략
자동화된 배포 시스템을 구축했다고 해서 끝은 아닙니다. 오히려 그 다음 단계인 서비스 운영이 더 중요합니다. 운영 중인 Spring Boot + Docker 애플리케이션은 예상치 못한 장애나 트래픽 증가 등 다양한 상황에 직면할 수 있으므로, 이에 대한 모니터링 및 대응 전략을 반드시 마련해야 합니다.
1. Docker 컨테이너 헬스체크 설정
Docker는 컨테이너 상태를 주기적으로 확인하는 헬스체크(HEALTHCHECK) 기능을 제공합니다. Spring Boot 애플리케이션이 정상인지 확인하기 위해 HTTP 응답 상태를 기준으로 설정할 수 있습니다.
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
위 설정은 Spring Boot Actuator의 헬스 엔드포인트를 기반으로 컨테이너 상태를 판단합니다. 컨테이너가 비정상 상태로 판단되면 자동으로 재시작 정책을 적용할 수도 있습니다.
2. 컨테이너 로그 확인 및 저장
Docker는 기본적으로 모든 로그를 표준 출력으로 기록합니다. 단기적으로는 다음 명령어로 실시간 로그 확인이 가능합니다:
docker-compose logs -f
docker logs [container_id]
하지만 운영 환경에서는 로그를 지속적으로 저장하고 시각화하기 위해 ELK Stack (Elasticsearch + Logstash + Kibana)이나 Fluentd + Grafana + Loki 조합을 사용하기도 합니다.
3. 메트릭 기반 모니터링 도구
시스템 리소스 및 애플리케이션 성능 모니터링을 위해 다음과 같은 오픈소스 도구들을 사용할 수 있습니다:
- Prometheus + Grafana: 메트릭 수집 및 시각화
- CAdvisor: Docker 컨테이너의 리소스 사용률 모니터링
- Spring Boot Actuator: 애플리케이션 상태 및 메트릭 제공
Prometheus는 Spring Boot 애플리케이션에서 Actuator 엔드포인트를 수집하여 Grafana를 통해 실시간 대시보드로 확인할 수 있도록 해줍니다.
4. 실패 시 롤백 전략
자동 배포는 빠른 반영이 가능하다는 장점이 있지만, 신규 배포 버전에서 장애가 발생했을 경우를 대비한 롤백 전략이 필요합니다. 이를 위해 다음과 같은 전략을 도입할 수 있습니다:
- Docker 이미지 버전 태그 관리: 이전 이미지로 쉽게 되돌릴 수 있도록 태깅
- docker-compose.yml 내 이미지 버전 명시:
image: myapp:1.2.3
와 같이 설정 - 자동화 파이프라인 내 수동 승인 단계 삽입: 운영 배포 전에 검증 및 승인을 요구
실제 롤백은 다음과 같은 방식으로 수행할 수 있습니다:
docker pull myapp:previous-tag
docker tag myapp:previous-tag myapp:latest
docker-compose down
docker-compose up -d
5. Slack 또는 이메일 알림 연동
GitHub Actions는 워크플로우 실행 결과에 따라 Slack, 이메일, Discord 등과 알림 연동이 가능합니다. 배포 성공 여부, 실패 알림 등을 즉시 받을 수 있도록 설정하여 실시간 대응 체계를 갖추는 것이 좋습니다.
정리: 자동화 이후의 안정성과 신뢰성 확보
지속적인 통합과 배포가 자동으로 이루어지는 시스템에서 가장 중요한 것은 운영 상태를 실시간으로 감지하고, 문제 발생 시 빠르게 대응할 수 있는 체계입니다. 이를 위해 모니터링, 로그 분석, 롤백, 알림 시스템이 유기적으로 구성되어야 합니다.
이제 마지막으로, 본 포스트 전체 내용을 종합하고 자동화 배포가 개발팀과 조직에 어떤 변화를 가져올 수 있는지 마무리 짓도록 하겠습니다.
10. 결론: 자동화 배포로 얻는 실질적 이득
우리는 지금까지 Spring Boot 애플리케이션을 Docker로 컨테이너화하고, Docker Compose를 통해 서비스를 통합한 뒤, GitHub Actions 기반의 CI/CD 파이프라인을 구축해 AWS EC2에 자동 배포하는 전 과정을 따라왔습니다. 이러한 일련의 자동화 프로세스는 단순한 기술적 진보에 그치지 않습니다. 그것은 곧 개발자와 조직이 일하는 방식의 본질적 변화를 의미합니다.
자동화 배포의 핵심 효과
- 신속한 피드백 루프: 코드 푸시 후 몇 분 내로 결과를 확인하고 대응 가능
- 안정성 향상: 수동 실수 제거, 일관된 배포 프로세스 확보
- 운영 비용 절감: 배포에 소요되는 시간과 인력을 최소화
- 스케일 대응력 강화: 서비스 성장 시, 자동화된 시스템으로 무리 없이 확장
무엇보다도, 개발자는 더 이상 배포를 걱정하지 않고 코드의 본질에 집중할 수 있으며, 운영팀은 로그 및 모니터링 체계를 통해 지속적인 관측과 대응이 가능해집니다.
지속 가능한 DevOps 문화로의 진입
CI/CD와 컨테이너 기반 배포 자동화는 단지 기술 도입이 아니라, 개발과 운영의 협업을 강화하고 문제 해결 속도를 향상시키는 DevOps 문화의 실현입니다. 지속적 개선(Continuous Improvement)의 기반 위에 빠르고 민첩한 제품 개선이 가능해지는 것입니다.
마지막 메시지: 코드를 넘어서 배포까지 책임지는 개발자
이제 개발자는 단지 기능을 구현하는 역할을 넘어, 코드를 실사용자에게 전달하는 전 과정을 설계하고 통제할 수 있는 역량을 갖추어야 합니다. 이번 시리즈를 따라오셨다면, 여러분도 더 이상 단순 개발자가 아닌, **운영까지 아우르는 ‘풀사이클 엔지니어’로 성장하는 출발점**에 서 계신 겁니다.
코드를 커밋하면 곧 서비스가 된다. 이 문장이 당연한 현실이 되는 환경, 지금 바로 실현해 보시기 바랍니다.