목표: main 브랜치 push/merge → GitHub Actions 빌드 → Docker 이미지 생성 → 레지스트리 푸시 → EC2에서 컨테이너 재기동
구성: Spring Boot(Gradle) + Docker + Docker Hub(또는 ECR) + EC2 + docker compose
CI/CD 파이프라인은 다음 순서로 동작한다.
1) 코드 푸시(main)
2) GitHub Actions CI: 빌드/테스트
3) Docker 이미지 빌드
4) Docker 레지스트리 푸시(Docker Hub/ECR)
5) EC2에서 최신 이미지 pull + 컨테이너 재기동(CD)
즉,
코드 푸시 → GitHub Actions 빌드 → Docker 이미지 생성 → 레지스트리 푸시 → EC2에서 컨테이너 재기동
main 브랜치에 push 되거나 PR merge가 되면, GitHub Actions 워크플로우가 자동 실행된다.
추가로 workflow_dispatch를 설정하면 GitHub UI에서 수동 실행도 가능하다.
GitHub Actions 러너(ubuntu-latest)에서:
bootJar)-x test)이 단계의 목적은 배포 전에 빌드 실패를 미리 걸러내는 것이다.
빌드 결과물(JAR)을 기반으로 Docker 이미지를 생성한다.
Dockerfile에 런타임 환경(JDK 이미지)과 실행 명령(java -jar)을 정의docker build로 이미지를 생성빌드된 Docker 이미지를 레지스트리(Docker Hub 또는 ECR)에 업로드한다.
docker login → docker pushEC2에서 해야 할 일은 단순하다.
가장 깔끔한 패턴은 docker-compose 기반으로 운영하는 것이다.
GitHub Actions를 사용하기 위해서는 레포지토리 루트에 다음 경로의 파일이 필요하다.
name: Deploy to EC2
on:
push:
branches: ["main"]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Set up Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew clean bootJar -x test
- name: Docker login
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | \
docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Docker build & push
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/myapp:latest .
docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
- name: Upload docker-compose.yml
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
source: "deploy/docker-compose.yml"
target: "/home/ec2-user/app"
overwrite: true
- name: Deploy on EC2
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
set -e
cd /home/ec2-user/app
docker compose pull
docker compose up -d
docker compose ps
| 이름 | 설명 |
|---|---|
| EC2_HOST | EC2 퍼블릭 IP 또는 도메인 |
| EC2_USER | EC2 로그인 유저 |
| EC2_SSH_KEY | EC2 Private Key (.pem 전체 내용) |
| DOCKER_USERNAME | Docker Hub 계정 ID |
| DOCKER_PASSWORD | Docker Hub Access Token |
| 이름 | 설명 |
|---|---|
| SPRING_PROFILES_ACTIVE | prod |
| DB_URL | DB 접속 URL |
| DB_USERNAME | DB 계정 |
| DB_PASSWORD | DB 비밀번호 |
| JWT_SECRET | JWT 서명 키 |
Dockerfile은 Docker 이미지를 생성하고 애플리케이션 실행 환경을 정의하는 파일이다.
FROM eclipse-temurin:21-jdk-jammy
WORKDIR /app
COPY build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]
애플리케이션 실행에 필요한 설정만 정의되므로 생각보다 내용이 단순하다.
version: "3.9"
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: always
env_file:
- .env
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-net
app:
image: ${DOCKER_USERNAME}/myapp:latest
container_name: myapp
restart: always
depends_on:
- mysql
env_file:
- .env
ports:
- "8080:8080"
networks:
- app-net
volumes:
mysql_data:
networks:
app-net:
driver: bridge
레포지토리 기준으로 다음 구조를 추천한다.
├─ .github/
│ └─ workflows/
│ └─ deploy.yml
├─ Dockerfile
└─ deploy/
└─ docker-compose.yml
CI/CD 설정에 필요한 문서와 설정 파일은 AI의 도움을 받아 빠르게 작성할 수 있다.
처음에는 모든 내용을 완벽히 이해하기 어렵지만, AI가 생성한 코드를 기준으로 하나씩 분석하며 학습하는 방식이 매우 효과적이라고 생각한다.
나 또한 이 방식을 통해 여러 기술들을 학습하고 있다.