📌 들어가며..
이번 글에서는 AWS EC2 인스턴스에 Docker와 Docker Compose 환경을 구성하고,
Spring Boot 서버를 Docker 이미지로 빌드해 배포하는 과정을 정리합니다.
또한, GitHub Actions를 활용한 CI/CD 자동화 파이프라인 구축까지 다룹니다.
⚠️ EC2 인스턴스와 RDS(MySQL) 인스턴스는 사전에 생성해 두었으며, 이 내용은 자세하게 다루지 않습니다.
이 글에서는 Docker 환경 구성 및 배포 자동화(CI/CD)를 집중적으로 다룹니다.
사전 준비된 환경
✅ EC2 인스턴스 정보
- OS : Ubuntu
- 타입 : t2.micro
- 탄력적 IP 연결
- 보안 그룹 : HTTP(80), HTTPS(443), SSH(22) 포트 오픈
✅ RDS 정보
- DB 엔진 : MySQL 8.0
- 포트 : 3306
✅ 그 외
- HTTPS 연결
- 가비아에서 구입한 도메인을 Route 53에 등록하고,
- ACM에서 SSL 인증서를 발급받아 로드밸런서에 연결하여 HTTPS 환경 구성
전체적인 흐름

대략적인 배포 구조는 위의 그림과 같습니다.
- EC2 인스턴스 내에 Docker와 Docker Compose를 설치하고,
- GitHub Actions를 통해 빌드한 Spring Boot 애플리케이션의 Docker 이미지를 Docker Hub에 푸시한 후,
- EC2에서는 해당 이미지를 pull 하여 컨테이너로 실행합니다.
📌 EC2에 Docker & Docker Compose 설치하기
EC2 인스턴스에서 Docker를 통해서 Spring Boot 애플리케이션이 실행되기 때문에 Docker와 Docker Compose를 설치해줘야 합니다.
EC2 인스턴스 접속
먼저 EC2 인스턴스 Ubuntu에 접속해줍니다.
AWS 콘솔에서 바로 접속할 수도 있고, 로컬 터미널에서 SSH로도 접속할 수 있는데, 저는 SSH로 접속하였습니다.
.pem 키 파일을 이용해서 접속합니다.
Docker & Docker Compose 설치
저는 아래와 같이 간단한 명령어를 통해 설치했습니다.
sudo apt update
sudo apt install docker.io
sudo apt install docker-compose
# 잘 설치되었는지 확인
docker --version
docker-compose --version
이 방식으로 설치하면 간단하고 빠르게 설치할 수 있다는 장점이 있습니다.
하지만, Docker가 아닌 docker.io 패키지를 설치하는 것이기 때문에 우분투 저장소에 있는 오래된 Docker 버전이 설치될 수 있어요.
그래서 Docker 공식 문서에서는 Docker 공식 레포지토리를 통해서 설치하는 방법을 권장하고 있습니다.
그래도 위의 방식대로 설치해도 실제 배포에 문제가 되진 않았어요!
이제 EC2에서 Docker와 Docker Compose 환경 구성이 완료되었습니다.
다음으로는, Docker Hub에 Spring Boot 애플리케이션 이미지를 빌드하고 푸시할 레포지토리를 생성해 보겠습니다.
📌 Docker Hub 레포지토리 생성하기
Docker Hub 공식 사이트에서 로그인 후 Repositories 메뉴로 이동해 Create a Repository 버튼을 클릭합니다.
레포지토리 이름을 입력하고, private으로 선택한 후 레포지토리를 생성합니다.
이제 이 레포지토리에 빌드한 Spring Boot 애플리케이션 이미지를 푸시할 수 있습니다.
📌 Spring Boot 애플리케이션을 Docker 이미지로 만들기
Docker Hub에 레포지토리를 만들었으니, 이제 Spring Boot 애플리케이션을 Docker 이미지로 빌드하기 위해 Dockerfile을 작성하겠습니다.
Dockerfile 작성
Dockerfile은 Docker 이미지의 빌드 과정을 정의하는 설정 파일로, 프로젝트 루트 디렉토리에 생성합니다.
# 사용할 베이스 이미지를 설정합니다.
# Spring Boot 애플리케이션을 실행하기 위해선 Java 환경이 필요하기 때문에
# Dockerfile 베이스 이미지로 openjdk를 사용합니다.
# 저는 21버전을 선택했습니다.
FROM openjdk:21
# build/libs/ 디렉토리 안에 있는 빌드된 JAR 파일을 가져와
# 그 JAR 파일을 컨테이너 내부로 복사합니다.
# 복사된 파일의 이름을 app.jar로 지정했습니다.
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
# Docker 컨테이너가 시작될 때 실행할 명령어입니다.
# app.jar 자바 애플리케이션을 실행한다는 의미입니다.
ENTRYPOINT ["java", "-jar", "/app.jar"]
현재 단계에서는 Dockerfile만 작성해 두고, 실제 Docker 이미지를 빌드하고 Docker Hub에 푸시하는 과정은
GitHub Actions를 통한 CI/CD 구축할 때 진행할 예정입니다.
📌 docker-compose로 실행 환경 구성하기
Docker 이미지를 빌드했으니, 이제 이 이미지를 기반으로 실제 동작하는 컨테이너를 띄워야겠죠?
CI/CD를 도입하면 이미지를 새로 빌드하고, 컨테이너를 띄우는 과정을 반복할 텐데
이럴 때마다 일일이 여러 설정들을 명령어로 입력하고 관리하는 것이 번거롭기도 하고, 실수가 발생할 수도 있어요.
그래서 이 과정을 쉽고 체계적으로 관리할 수 있도록 도와주는 도구가 바로 docker-compose입니다.
docker-compose.yml 작성
version: '3' # Docker Compose 버전
services: # 컨테이너로 실행할 서비스들
app: # 하나의 서비스 이름 - 여기서는 Spring Boot를 실행할 컨테이너
image: ${DOCKER_USERNAME}/my-repository-name:${TAG} # 컨테이너를 실행할 Docker 이미지 이름
container_name: my-container-name # 실행될 컨테이너 이름
ports: # 포트 포워딩 설정
- "80:8080" # EC2 서버의 80번 포트를 컨테이너 내부의 8080번 포트로 연결
env_file: # env 파일 설정
- .env
environment: # 컨테이너 내부의 환경 변수를 직접 지정
- TZ=Asia/Seoul # 한국 시간(KST)으로 설정
volumes: # 외부에 데이터 저장할 디렉토리 설정
- /home/ubuntu/logs:/home/logs # 컨테이너가 생성하는 로그 파일을 우분투 디렉토리에 저장
restart: always # 컨테이너가 예기치 않게 중단되었을 때, 자동으로 재시작하도록 설정
저는 프로젝트 루트에 docker-compose 디렉토리를 만들고 그 안에 docker-compose.yml을 넣었습니다.
그리고 이미지 이름을 설정할 때 쓰이는 ${DOCKER_USERNAME}과 ${TAG}는 각각 Docker Hub 사용자명과 이미지 태그를 나타내는 환경변수입니다. 이 값들은 이후에 CD 단계에서 .env 파일을 통해 설정해 줍니다.
📌 GitHub Actions로 CI/CD 배포 자동화하기
이제 Spring Boot 애플리케이션을 자동으로 빌드하고 배포할 수 있도록 GitHub Actions를 사용해 CI/CD를 구축해 보겠습니다.
✔️ CI/CD란?
- CI (Continuous Integration, 지속적인 통합)
- 자동으로 빌드를 수행합니다.
- 코드 충돌을 조기에 발견하고 해결하기 위해 사용됩니다.
- 본 포스팅에서는 develop 또는 main 브랜치에 Pull Request를 올리면 CI가 실행되도록 설정했습니다.
- CD (Continuous Deployment, 지속적인 배포)
- CI를 통해서 통합된 코드가 빌드에 통과하면, 자동으로 배포까지 진행합니다.
- 본 포스팅에서는 main 브랜치에 코드가 푸시되면 CD가 실행되도록 설정했습니다.
GitHub Secrets 설정
GitHub Actions 워크플로우에서 사용할 민감한 정보들은 코드에 직접 노출하면 안 되고, GitHub Secrets로 설정해줘야 합니다.
GitHub 레포지토리에서 Settings -> Secrets and variables -> Actions 로 이동해서 new repository secret 버튼을 눌러 시크릿을 등록해 줍니다.

등록해야 할 Secrets
- APPLICATION_YML : application.yml 파일을 base64로 인코딩한 값
- DB_PASSWORD : RDS DB 비밀번호
- DB_URL : RDS 주소
- DB_USERNAME : RDS DB 사용자명
- DOCKER_PASSWORD : Docker Hub 비밀번호
- DOCKER_USERNAME : Docker Hub 사용자명
- EC2_HOST : EC2 퍼블릿 IP 또는 탄력적 IP
- EC2_SSH_KEY : EC2에 접속할 SSH 키(.pem)를 base64로 인코딩한 값
CI 워크플로우 파일 ci.yml 작성
GitHub Actions에서 워크플로우 파일을 인식할 수 있도록
ci.yml와 cd.yml은 프로젝트 루트에 .github/workflows 디렉토리를 만들고 그 안에 저장했습니다.
name: CI
# develop 브랜치와 main 브랜치로 Pull Request를 올렸을 때 실행됩니다.
on:
pull_request:
branches:
- develop
- main
jobs:
build:
runs-on: ubuntu-latest # GitHub Actions는 ubuntu-latest 환경에서 실행됩니다.
env:
# RDS 관련 정보(GitHub Secrets)를 env로 설정합니다.
DB_URL: ${{ secrets.DB_URL }}
DB_USERNAME: ${{ secrets.DB_USERNAME }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
steps:
# GitHub 레포지토리 코드를 가져옵니다.
- name: Checkout Code
uses: actions/checkout@v3
# Java 21 환경으로 실행합니다.
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
# base64로 인코딩된 application.yml을 디코딩해서 프로젝트 내부(src/main/resources)에 생성합니다.
- name: Create application.yml
run: |
mkdir -p src/main/resources
echo "${{ secrets.APPLICATION_YML }}" | base64 --decode > src/main/resources/application.yml
# Gradle 빌드를 실행합니다.
- name: Grant execute permission to Gradle wrapper
run: chmod +x ./gradlew
- name: Build (no tests)
run: ./gradlew clean build -x test # 테스트도 하고 싶다면 -x test를 지우면 됩니다.
CD 워크플로우 파일 cd.yml 작성
name: CD
# main 브랜치에 코드가 push될 때 실행됩니다.
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
env:
DB_URL: ${{ secrets.DB_URL }}
DB_USERNAME: ${{ secrets.DB_USERNAME }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Create application.yml
run: |
mkdir -p src/main/resources
echo "${{ secrets.APPLICATION_YML }}" | base64 --decode > src/main/resources/application.yml
- name: Grant execute permission to Gradle wrapper
run: chmod +x ./gradlew
- name: Build (no tests)
run: ./gradlew clean build -x test
# 여기까지 CI 과정과 동일하게 빌드
# Docker Hub에 로그인합니다.
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# 애플리케이션을 Docker 이미지로 빌드하고, Git 커밋 해시(github.sha)를 태그로 설정해서 Docker Hub에 push 합니다.
- name: Build and Push Docker Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/snap-ticket:${{ github.sha }} .
docker push ${{ secrets.DOCKER_USERNAME }}/snap-ticket:${{ github.sha }}
# Docker 안에서 사용할 env 파일을 생성합니다.
# docker-compose.yml에서 사용할 환경 변수들을 설정합니다.
- name: Create .env file
run: |
echo "DB_URL=${{ secrets.DB_URL }}" > .env
echo "DB_USERNAME=${{ secrets.DB_USERNAME }}" >> .env
echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> .env
echo "TAG=${{ github.sha }}" >> .env
echo "DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}" >> .env
# EC2 서버로 docker-compose.yml과 .env 파일을 업로드합니다.
- name: Upload docker-compose.yml, .env to EC2
run: |
echo "${{ secrets.EC2_SSH_KEY }}" | base64 -d > myKey.pem
chmod 600 myKey.pem
scp -o StrictHostKeyChecking=no -i myKey.pem docker-compose/docker-compose.yml ubuntu@${{ secrets.EC2_HOST }}:~/my-project/docker-compose.yml
scp -o StrictHostKeyChecking=no -i myKey.pem .env ubuntu@${{ secrets.EC2_HOST }}:~/my-project/.env
# EC2에 이전에 돌아가고 있던 컨테이너가 있다면 중지하고, 새로운 이미지를 pull받아 컨테이너를 재시작합니다.
- name: Run docker-compose on EC2
run: |
ssh -o StrictHostKeyChecking=no -i myKey.pem ubuntu@${{ secrets.EC2_HOST }} <<EOF
docker login -u "${{ secrets.DOCKER_USERNAME }}" -p "${{ secrets.DOCKER_PASSWORD }}"
cd /home/ubuntu/my-project
docker-compose down || true
docker-compose pull
docker-compose up -d
EOF
rm myKey.pem
이제 작성한 파일들(Dockerfile, docker-compose.yml, ci.yml, cd.yml)을 develop 브랜치에 PR로 올려 CI가 실행되도록 하고, 이후 main 브랜치에 PR을 머지하고 푸시하면 자동으로 배포가 진행됩니다.
'Infra' 카테고리의 다른 글
| [Docker] 도커에 대해 알아보자 (0) | 2025.06.13 |
|---|
