목차
- 로컬 개발 환경 구성
- AWS 인프라 구축
- CI/CD 자동화
많은 과정이 내포되어 있지만, 함축해서 작성했다.
Step1: 로컬 개발 환경 구성 (Spring Boot + Docker)
혼자 개발할 땐 로컬 DB만 깔아서 쓰면 그만이지만, 팀 프로젝트와 배포까지 고려한다면 처음부터 격리된 환경(Docker)을 잡고 가는 것이 좋다고 판단했다.
1. 기술 스택 (Tech Stack)
가장 보편적이고 안정적인 스택을 선정했다.
- Language: Java 17
- Framework: Spring Boot 3.5.9
- Database: MySQL 8.0
- Build Tool: Gradle
2. 보안과 환경 변수 관리 (.env)
처음 환경 세팅을 할 때 MySQL user, password를 그대로 노출하는 것에 거부감이 느껴졌다. 이런 상황은 어떻게 해결할 수 있을까? 문제상황 → 해결책으로 정리해보자.
문제 상황
데이터베이스 비밀번호나 민감한 정보를 코드(application.yml)에 하드코딩하면 GitHub에 그대로 노출된다.
협업 시 팀원마다 DB 설정이 다를 수 있어 수정이 번거롭다.
해결책: 환경 변수 분리
민감한 정보는 .env 파일로 빼고, Git에서 제외(gitignore)한다.
- .env 파일 생성 (프로젝트 루트)
- 3307 Port를 Local에서 사용하도록 했다.
- macOS의 경우 MySQL이 Local에 있을 때 3306을 잡아놓아서 3307로 바꿨다.
# .gitignore에 반드시 추가할 것!
# MySQL 설정 (Docker 컨테이너용)
MYSQL_ROOT_PASS=root
DB_NAME=campus_form
MYSQL_USER_NAME=campusform
MYSQL_USER_PASS=campusform1234
# Spring Boot 접속 설정 (로컬 실행용)
# 로컬 포트 충돌 방지를 위해 3307 포트 사용
DB_URL=jdbc:mysql://localhost:3307/campus_form?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
DB_USERNAME=campusform
DB_PASSWORD=campusform1234
- application.yml 수정
- 스프링 부트가 실행될 때 환경 변수나 시스템 속성에서 값을 가져오도록 변경한다.
- 환경 변수를 IntellJ에서 주입을 해줘야 하는데, 이는 프로젝트 팀원들과의 공유하는 파일을 넣거나, 직접 값을 Edit Configuration에서 넣어줄 수 있다.
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
- 도커 이미지 빌드 (Dockerfile)
- 애플리케이션을 배포하려면 실행 가능한 이미지로 만들어야 한다.
- Multi-stage Build를 사용하여 빌드 도구(Gradle) 없이도 실행 가능한 경량 이미지를 만든다.
[Dockerfile 작성]
# 1. Builder Stage (빌드 환경)
FROM gradle:7.6-jdk17 AS builder
WORKDIR /build
COPY build.gradle settings.gradle /build/
RUN gradle build -x test --parallel --continue > /dev/null 2>&1 || true
COPY src /build/src
RUN gradle build -x test --parallel
# 2. Runner Stage (실행 환경)
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/build/libs/*SNAPSHOT.jar app.jar
ENV TZ=Asia/Seoul
ENTRYPOINT ["java", "-jar", "app.jar"]
- 로컬 오케스트레이션 (docker-compose)
- 로컬 개발의 딜레마
- DB: 팀원 모두가 똑같은 DB 버전과 설정을 써야 함 -> Docker로 실행
- App: 코드 수정 시마다 이미지를 다시 빌드하면 너무 느림 -> IntelliJ에서 실행
- 해결책: 하이브리드 구성
- DB는 Docker로 띄우고, 애플리케이션은 로컬(IntelliJ)에서 띄운다.
- 이때 포트 충돌을 막는 것이 핵심이다.
- 로컬 개발의 딜레마
[docker-compose.yml 작성]
services:
db:
image: mysql:8.0
container_name: campus-mysql
ports:
- "3307:3306" # 호스트(3307) -> 컨테이너(3306) 포트 포워딩
environment:
# .env 파일의 변수를 그대로 가져옴
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASS}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${MYSQL_USER_NAME}
MYSQL_PASSWORD: ${MYSQL_USER_PASS}
volumes:
- ./db_data:/var/lib/mysql # DB 데이터 영구 저장
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
healthcheck:
# DB가 완전히 뜰 때까지 기다리기 위한 설정
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASS}"]
interval: 5s
timeout: 5s
retries: 10
이 세팅을 통해 팀원들은 다음과 같은 루틴으로 개발할 수 있다.
- 초기 설정: git clone 후 .env 파일 생성 (팀 공유 템플릿 활용)
- DB 실행: docker-compose up -d db (3307 포트로 MySQL 실행)
- 앱 실행: IntelliJ에서 Run (소스 코드 수정 시 즉시 반영)
장점
- 일관성: 누가 실행하든 동일한 DB 환경 보장
- 편의성: 로컬에 MySQL을 직접 설치할 필요 없음
- 보안: 비밀번호가 코드에 남지 않음
Step2: AWS 클라우드 환경 구축 및 보안 전략
로컬 개발 환경이 준비되었다면, 이제 코드가 실제로 돌아갈 "진짜 서버(Production)"를 만들 차례다. 단순히 서버만 만드는 것이 아니라, 최소한의 비용으로 보안까지 챙기는 세팅 방법을 정리한다.
서버 인스턴스 생성 (EC2)
- 팀 프로젝트 규모에 적합하면서도 비용이 들지 않는 프리 티어(Free Tier) 구성을 선택했다.
- OS: Ubuntu Server 24.04 LTS
- 가장 레퍼런스가 많고, 도커(Docker)와의 호환성이 뛰어남.
- Instance Type: t2.micro
- vCPU 1개, RAM 1GB. 트래픽이 적은 초기 프로젝트에 적합하며 월 750시간 무료.
- Storage: 20GB (gp3)
- 기본 8GB는 도커 이미지가 쌓이면 금방 부족해지므로, 무료 한도(30GB) 내에서 넉넉하게 설정.
- OS: Ubuntu Server 24.04 LTS
네트워크 보안 전략 (Security Group)
- 모든 포트를 0.0.0.0/0 (전체 허용)으로 열어두는데, 이는 보안 위험을 초래한다.
- 보안은 아래와 같은 원칙을 적용했다.
| 포트 | 서비스 | 소스(허용 범위) | 정책 설명 |
| 8080 | Spring Boot | 0.0.0.0/0 | 누구나 웹 서비스에 접속할 수 있어야 하므로 전체 허용. |
| 22 | SSH (원격 접속) | 0.0.0.0/0 | 팀원들의 IP가 유동적이므로 전체 허용하되, Key Pair(.pem) 파일 관리로 보안 유지. |
| 3306 | MySQL | 개방 안 함 | [핵심] DB 포트는 절대 외부로 노출하지 않는다. |
그럼 DB 확인은 어떻게 함? (SSH Tunneling)
- DB 포트를 닫아버리면 로컬 DB 툴(IntelliJ Database 등)에서 접속할 수 없게 된다. 이때 SSH 터널링(SSH Tunneling) 기술을 사용한다.
- 원리: 22번 포트(SSH)를 통해 서버 내부에 안전하게 진입한 뒤, 서버 내부에서 로컬(localhost) DB에 접속하는 방식.
- 장점: 3306 포트를 전 세계에 노출하지 않고도 안전하게 DB 관리가 가능함.
고정 IP 할당 (Elastic IP)
- AWS EC2는 재부팅 시 IP가 변경된다. 배포 설정(GitHub Actions)이나 도메인 연결이 끊기는 것을 막기 위해 탄력적 IP (Elastic IP)를 할당하여 고정 주소를 확보했다.
- 주의: 할당받은 IP를 인스턴스에 연결하지 않으면 과금되므로, 생성 즉시 연결해야 한다.
서버 초기화 (Docker 환경 구성)
- 서버를 처음 생성하면 아무것도 없는 깡통이다. 로컬과 동일한 환경을 보장하기 위해 Docker를 설치한다.
- GitHub Actions가 이 서버에 접속해 명령을 내릴 것이므로, docker-compose까지 미리 세팅해둔다.
[초기 세팅 명령어]
# 1. 도커 설치 (우분투 기준)
sudo apt-get update
sudo apt-get install -y docker.io
sudo apt-get install -y docker-compose
# 2. 권한 부여 (sudo 없이 docker 쓰기 위해)
sudo usermod -aG docker $USER
# (여기서 로그아웃 했다가 다시 접속해야 적용됨) exit -> ssh 재접속
# 3. 프로젝트 폴더 생성 및 이동
mkdir ~/campus-project
cd ~/campus-project
# 4. .env 파일 생성 (서버용)
nano .env
# (Step 5의 내용을 복붙하고 Ctrl+X, Y, Enter 저장)
# 5. docker-compose.yml 생성 (서버용 - image 사용 버전)
nano docker-compose.yml
# (build: . 부분을 지우고 image: [도커아이디]/campus-server:latest 로 바꾼 내용 작성)
정리
- 이 과정을 통해 우리는 다음과 같은 인프라를 구축했다.
- 비용 효율: 프리 티어를 활용해 0원으로 서버 구축.
- 보안 강화: 3306 포트를 원천 봉쇄하고 SSH 터널링 활용.
- 환경 일치: 서버에도 로컬과 똑같이 Docker 환경 구성.
Step3: CI/CD 파이프라인 구축 (GitHub Actions)
로컬 개발 환경과 AWS 서버가 준비되었다면, 자동화를 통해 개발자가 코드를 main 브랜치에 올리기만 하면, 테스트부터 배포까지 진행되는 CI/CD 파이프라인을 구축한다.
배포 아키텍처 (Workflow)
- 구축할 배포 흐름은 다음과 같다.
- Push: 개발자가 GitHub main 브랜치에 코드를 푸시한다.
- CI (Build): GitHub Actions가 코드를 받아 테스트하고 빌드한다.
- Docker Push: 빌드된 결과물을 Docker Image로 만들어 Docker Hub에 업로드한다.
- CD (Deploy): GitHub Actions가 AWS EC2에 접속해 "새 이미지를 받아와서 실행해!"라고 명령한다.
GitHub Actions 설정 (deploy.yml)
- 프로젝트 내 .github/workflows/deploy.yml 파일을 생성하여 파이프라인을 정의했다.
- 배포용 설정 파일(docker-compose.prod.yml)을 명시적으로 사용하는 것이다.
name: Deploy to EC2
on:
push:
branches: [ "main" ] # main 브랜치에 푸시될 때만 실행
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. 코드 체크아웃 및 JDK 설정
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# 2. 스프링 부트 빌드 (테스트 제외 옵션 사용)
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew build -x test
# 3. 도커 이미지 빌드 & 푸시
- name: Build and Push Docker Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/campus-server:latest .
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker push ${{ secrets.DOCKER_USERNAME }}/campus-server:latest
# 4. EC2 접속 및 배포
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
script: |
mkdir -p ~/campus-project
cd ~/campus-project
# 배포용 설정파일(-f)을 지정해서 실행
sudo docker-compose -f docker-compose.prod.yml pull
sudo docker-compose -f docker-compose.prod.yml up -d
sudo docker image prune -f
보안 키 관리 (GitHub Secrets)
- 서버 IP, SSH 키, 도커 비밀번호 같은 민감 정보는 절대 코드에 노출하면 안 된다.
- GitHub Repository Settings -> Secrets and variables에 등록하여 안전하게 주입했다.
- DOCKER_USERNAME / DOCKER_PASSWORD: 도커 허브 로그인 정보
- HOST: AWS EC2 탄력적 IP (Elastic IP)
- KEY: EC2 접속용 .pem 파일 내용 전문
- USERNAME: ubuntu
배포용 설정 분리 (docker-compose.prod.yml)
- 로컬 개발에선 build: .을 사용해 코드를 바로 실행하지만, 서버에선 이미지를 받아와야(image: ...) 한다.
- 따라서 운영 환경 전용 설정 파일을 별도로 만들어 서버에 업로드했다.
- Local (docker-compose.yml): 개발 편의성 중심. 로컬 포트 3307 사용.
- Prod (docker-compose.prod.yml): 안정성 중심. 도커 이미지 Pull 방식 사용. 컨테이너 재시작 정책(restart: always) 적용.