← 목록으로

프론트엔드 개발자의 백엔드 서버 배포기 (1) – Java Spring Boot 백엔드 배포 + Docker 도입기

YEJI · 2025. 04. 04. 15:00 · 5분
목차

서론

시작하기에 앞서, 다른 서버를 배포하고 나니 이 방법은 별로 좋지 않다는 판단을 하여 이 글을 통해 배포하시는 것은 추천하지 않습니다!

이전 프로젝트에서 백엔드 담당자분이 서버를 내리시게 되었고, 해당 프로젝트가 다시 필요해졌습니다.
기왕 이렇게 된 김에 직접 배포해보고 싶다는 마음이 생겨, GPT와 Google의 힘을 빌려 백엔드 서버 배포에 도전했습니다.
하지만 결과는 계속 실패...

프론트엔드는 검색과 공식문서 등을 참고해 AWS를 통해 HTTPS로 배포한 경험이 있어서 ‘백엔드도 그냥 따라 하면 되겠지’ 싶었는데, 현실은 달랐습니다.
정작 제가 원하는 수준의 내 환경과 일치하는 실질적인 백엔드 배포 튜토리얼은 찾기 힘들었어요.

그래서 방향을 바꿔, GPT에게 구체적인 요구사항을 주고 단계별로 질문하며 하나씩 배워 나가는 방식으로 접근했습니다.
삽질도 많았지만, 결국 성공했고, 향후 재배포 시를 대비해 A to Z 가이드 형식으로 정리해두려고 합니다.


시작 전: 나의 상황 정리

  • 백엔드 개발 경험은 거의 없음
  • Java나 Spring Boot 문법은 잘 모르지만, 기능 구현 논의 과정은 기억함
  • 서버 접속이나 EC2 초기 설정 정도는 익숙함
  • GPT와 Google에 전적으로 의존하며 시도

왜 Docker를 도입했는가?

초기에는 Docker 없이 배포를 시도했습니다.
EC2에 직접 Spring Boot 프로젝트를 클론하고 빌드한 후, Nginx로 도메인 연결하는 방식이었는데...
계속해서 실패했습니다. 당시에는 이유를 몰랐는데 배포하면서 알아보니 환경 차이와 설정 누락이 계속 문제를 일으킨 것으로 보입니다.

그래서 이번에는 처음부터 Docker를 도입하여 환경을 컨테이너화하고, **Docker Compose로 의존 서비스(MySQL, Redis 등)**까지 한 번에 실행하는 전략으로 바꾸었습니다.

그 결과, 처음으로 DB 테이블이 잘 생성되고 서버가 꺼지지 않는 것을 봤습니다. (감격 🥹) 매번 바로 꺼지는 것만 봤지 DB 테이블 생성되는거 처음봤습니다..

물론 DB 테이블 생성된 뒤에도 계속 종료되었는데 그건 아래에서 설명하겠지만 설정 누락 때문이었습니다. ex. jwt 토큰, 리셋 패스워드를 보낼 email, email의 앱 비밀번호 등...

장점설명
✅ 환경 일치내 로컬 환경을 그대로 복제 가능
✅ 재사용성컨테이너 하나 만들면 어디서든 실행 가능
✅ 간편한 배포docker-compose 한 줄로 여러 서비스 실행
✅ 유지보수 쉬움설정이 코드로 남아 있어서 추적/수정 쉬움
✅ 의존성 격리시스템에 직접 설치하지 않고도 사용 가능

🤖 GPT 사용 전략

GPT에게 "AWS 서버 배포 튜토리얼에 가장 적합한 엔진은 누구냐"고 물었고, 여러 엔진을 직접 비교해본 결과 gpt-4o가 가장 안정적으로 가이드를 제공했습니다. (처음엔 물어본 엔진이 계속 자기도 할 수 있다고 해서 모든 엔진을 나열하고 물어봤는데 4o를 추천하길래 4o로 진행했습니다)

특히, 모든 단계를 한 번에 알려달라는 식보다는 단계별로 하나씩 질문하고 진행하는 방식이라 백엔드 서버 배포의 지식이 거의 없는 저에게 좋았습니다.


🧱 백엔드 기술 스택

  • Spring Boot 2.7.x / Java 17
  • MySQL 8
  • Redis
  • Spring Security + JWT
  • QueryDSL + JPA
  • Swagger
  • JUnit5 / H2 (Test용)
  • Docker + GitHub Actions (CI/CD용)
  • AWS EC2 (Ubuntu 24.04)
  • 도메인: 구매한 도메인 (SSL 적용)

1. EC2 인스턴스 접속

bash
ssh -i [your-key.pem] ubuntu@[EC2 퍼블릭 IP]

2. EC2 서버 초기 세팅

🔧 2-1. 시스템 패키지 업데이트

bash
sudo apt update && sudo apt upgrade -y

문제 발생:
Ubuntu 24.04 (noble)에서는 certbot PPA가 아직 공식적으로 지원되지 않음 → Snap으로 설치 필요

✅ Certbot 설치 (Snap 방식)

bash
# 기존 PPA 제거
sudo add-apt-repository --remove ppa:certbot/certbot
 
# Snap 기반 Certbot 설치
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
 
# 명령어 심볼릭 링크 설정
sudo ln -s /snap/bin/certbot /usr/bin/certbot

정상 설치 확인:

bash
certbot --version

🔧 2-2. 필수 패키지 설치

bash
sudo apt install -y git curl unzip wget gnupg lsb-release vim ufw

🔧 2-3. Docker 설치

bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Docker Compose 설치:

bash
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.22.0/docker-compose-linux-x86_64 \
-o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose

도커 권한 부여:

bash
sudo usermod -aG docker $USER

⚠️ 반드시 로그아웃 후 재접속 또는 newgrp docker 실행 필요

설치 확인:

bash
docker --version
docker compose version

🔧 2-4. Java 17 설치

bash
sudo apt install openjdk-17-jdk -y
java -version

3. Spring Boot 프로젝트 GitHub에서 클론

bash
cd ~
git clone https://github.com/your-username/your-repo.git
cd your-repo

4. 환경 설정 파일 작성

작성할 파일 목록:

파일명위치설명
Dockerfile루트JAR 파일을 기반으로 Spring Boot 애플리케이션 실행
docker-compose.yml루트Spring, MySQL, Redis 컨테이너 통합 실행
application-prod.ymlsrc/main/resources프로덕션 환경 전용 설정
.env루트민감한 설정 값 관리 (환경 변수화)

❗ 주의:
소스 코드에서 @Value로 선언된 모든 항목을 .env와 application-prod.yml에서 반드시 설정해야 합니다. 또 EC2에 저장되는 이미지를 불러와야하기 때문에 이미지 경로 또한 설정합니다. 설정하지 않으면 이미지가 url 형식이 아닌 images/이미지이름.확장자로 데이터가 넘어오기 때문에 확인해보셔야합니다. 백엔드에서 이미지 처리하는 여러 방법이 있지만 기존 프로젝트에서는 이렇게 하는 것 같아서 이 방법으로 진행했습니다.

예시:

java
@Value("${jwt.secret}")
@Value("${props.allowed-origin-url}")
@Value("${props.allowed-origin-https-url}")
@Value("${spring.mail.username}")
@Value("${props.reset-password-url}")

5. Dockerfile

dockerfile
FROM amazoncorretto:17
ARG JAR_FILE=build/libs/프로젝트-0.0.1-SNAPSHOT.jar
RUN mkdir -p /app/images
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "/app.jar"]

6. docker-compose.yml

yaml
version: "3.8"
 
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
 
redis:
image: redis:latest
container_name: redis
restart: unless-stopped
ports:
- "6379:6379"
 
app:
build:
context: .
dockerfile: Dockerfile
container_name: spring-app
depends_on:
- mysql
- redis
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
SPRING_REDIS_HOST: ${SPRING_REDIS_HOST}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
MAIL_USERNAME: ${MAIL_USERNAME}
MAIL_PASSWORD: ${MAIL_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
ALLOWED_ORIGIN_URL: ${ALLOWED_ORIGIN_URL}
ALLOWED_ORIGIN_HTTPS_URL: ${ALLOWED_ORIGIN_HTTPS_URL}
RESET_PASSWORD_URL: ${RESET_PASSWORD_URL}
volumes:
- image-data:/app/images
 
volumes:
mysql-data:
image-data:

7. application-prod.yml

yaml
port: 8080
 
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
 
redis:
host: ${SPRING_REDIS_HOST}
port: 6379
 
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100
 
servlet:
multipart:
max-file-size: 1MB
max-request-size: 10MB
 
mail:
host: smtp.gmail.com
port: 587
username: ${MAIL_USERNAME}
password: ${MAIL_PASSWORD}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
web:
resources:
static-locations: file:/app/images/
 
springdoc:
swagger-ui:
path: /swagger
api-docs:
path: /api-docs
swagger-ui-url: /swagger-ui.html
server-url: https://duck-map.shop
jwt:
secret: ${JWT_SECRET}
 
props:
allowed-origin-url: ${ALLOWED_ORIGIN_URL}
allowed-origin-https-url: ${ALLOWED_ORIGIN_HTTPS_URL}
reset-password-url: ${RESET_PASSWORD_URL}
image-dir: /app/images

8. .env

env
# MySQL
MYSQL_ROOT_PASSWORD=루트비밀번호
MYSQL_DATABASE=데이터베이스이름
MYSQL_USER=이름
MYSQL_PASSWORD=비밀번호
# Spring Datasource (app 서비스에서 사용)
SPRING_DATASOURCE_URL=url
SPRING_DATASOURCE_USERNAME=이름
SPRING_DATASOURCE_PASSWORD=비밀번호
# Redis
SPRING_REDIS_HOST=redis
# Spring Profile
SPRING_PROFILES_ACTIVE=prod
# Mail
MAIL_USERNAME=이메일
MAIL_PASSWORD=비밀번호
# JWT
JWT_SECRET=토큰
# Frontend URL (for CORS and reset password link)
ALLOWED_ORIGIN_URL=url
ALLOWED_ORIGIN_HTTPS_URL=url
RESET_PASSWORD_URL=url

9. 빌드 및 실행

📦 JAR 파일 빌드

bash
./gradlew bootJar

JAR 파일 생성 위치:

text
build/libs/app-0.0.1-SNAPSHOT.jar

🚀 Docker Compose 실행

bash
docker compose up --build

다음 편에서는 도메인 연결과 HTTPS 설정, Nginx 리버스 프록시 구성 그리고 약간의 기존의 백엔드 코드를 수정한 것을 다룰 예정입니다! 😎