11a Docker 개념·핵심 요소 📚 전체 맵 11c docker-compose·실행
Series 11 · Docker · 2/3

DOCKER
FILE
BUILD

Dockerfile 작성법 · 멀티스테이지 빌드 · Spring Boot · React + Nginx
나만의 이미지를 만드는 레시피 파일

11a 개념·핵심 요소 11b Dockerfile 작성 11c docker-compose·실행
01~10 Java·Spring·JPA·Bean 11 Docker 12 용어사전·면접Q&A
이 파일의 학습 목적
11b — Dockerfile 작성 · 멀티스테이지 빌드 · React + Nginx

Dockerfile은 내 앱을 Docker 이미지로 만드는 레시피 파일이다. 빌드 환경(~800MB)과 실행 환경(~200MB)을 분리하는 멀티스테이지 빌드로 최종 이미지 크기를 대폭 줄이고, React는 Nginx로 서빙하며 /api/ 프록시도 설정한다.

이 파일에서 배울 것
  • Dockerfile 명령어 — FROM COPY RUN ENTRYPOINT
  • Spring Boot Dockerfile — 멀티스테이지 빌드
  • React Dockerfile — node:18-alpine → nginx:alpine
  • nginx.conf — React Router + /api/ 프록시
  • application.properties — 환경변수 주입 패턴
프로젝트 코드 연결
  • · board/Dockerfile — gradle:8.5-jdk21 → openjdk:21-slim
  • · board-frontend/Dockerfile — node → nginx
  • · nginx.confproxy_pass http://springboot:8080
  • · ${SPRING_DATASOURCE_URL:기본값} 패턴
시리즈 순서: 11a Docker 개념 → 11b ← 지금 여기 → 11c docker-compose·실행
01 — Dockerfile이란?
나만의 Docker 이미지를 만드는 레시피 파일
📋 Dockerfile = 이미지 생성 레시피 Dockerfile
🍳 비유 — 요리 레시피
레시피(Dockerfile) → 요리 과정(빌드) → 완성된 요리(이미지)
"Java 설치 → 내 jar 파일 복사 → 실행" 과정을 자동화
docker build 명령어로 Dockerfile을 읽어 이미지 생성
Dockerfile — 주요 명령어
FROM # 베이스 이미지 지정 (출발점)
WORKDIR # 컨테이너 안 작업 디렉토리 설정
COPY # 파일을 컨테이너 안으로 복사
RUN # 빌드 시점에 명령어 실행 (gradle build, npm install)
EXPOSE # 사용할 포트 명시 (문서화 목적)
ENTRYPOINT # 컨테이너 시작 시 실행할 명령어
CMD # ENTRYPOINT의 기본 인자 (또는 단독 실행 명령)
ENV # 환경변수 설정
ARG # 빌드 시점 인자
02 — Spring Boot Dockerfile
멀티스테이지 빌드 — 빌드(~800MB) → 실행(~200MB) 이미지 분리
파일 위치
board/ (Spring Boot 프로젝트 루트)
├── src/
├── build.gradle
├── gradlew
└── Dockerfile ← 여기에 생성
1단계 — 빌드
gradle:8.5-jdk21
내 코드 복사
gradle build 실행
→ jar 파일 생성
~800MB (빌드 도구 포함)
2단계 — 실행
openjdk:21-slim
jar 파일만 복사
java -jar 실행

~200MB (실행에 필요한 것만)
Dockerfile — board/Dockerfile 완전 해설
# ─── 1단계: 빌드 단계 ───────────────────────────────────────
FROM gradle:8.5-jdk21 AS build
# gradle:8.5-jdk21 이미지 기반 (Java 21 + Gradle 포함)
# AS build → 이 단계에 이름 붙임 (2단계에서 참조)

WORKDIR /app
# 컨테이너 안에서 작업 폴더를 /app으로 설정

COPY . .
# 내 프로젝트 전체를 컨테이너 /app으로 복사

RUN gradle build -x test --no-daemon
# Gradle로 빌드 실행
# -x test → 테스트 제외 (빠른 빌드)
# --no-daemon → 백그라운드 데몬 없이 실행
# 결과: /app/build/libs/board-0.0.1-SNAPSHOT.jar 생성

# ─── 2단계: 실행 단계 ───────────────────────────────────────
FROM openjdk:21-slim
# 실행에 필요한 Java 21만 있는 가벼운 이미지
# 빌드 도구(Gradle) 없음 → 이미지 크기 대폭 감소

WORKDIR /app

COPY --from=build /app/build/libs/*.jar app.jar
# 1단계(build)에서 만든 jar 파일만 복사
# *.jar → build 폴더 안의 jar 파일 자동 선택

EXPOSE 8080
# 8080 포트 사용 명시 (문서화 목적)

ENTRYPOINT ["java", "-jar", "app.jar"]
# 컨테이너 시작 시: java -jar app.jar 실행
# = Spring Boot 애플리케이션 시작
💡 멀티스테이지 빌드를 쓰는 이유
1단계 gradle:8.5-jdk21 = Gradle + Java 21 → 이미지 크기 약 800MB
2단계 openjdk:21-slim = Java 21만 → 이미지 크기 약 200MB
빌드는 1단계에서, 실행은 2단계에서 → 최종 이미지 크기 75% 감소!
03 — React Dockerfile + Nginx
npm 빌드 → Nginx로 정적 파일 서빙 + /api/ 프록시
⚛️ 파일 위치
board-frontend/ (React 프로젝트 루트)
├── src/
├── public/
├── package.json
├── Dockerfile ← 여기에 생성
└── nginx.conf ← 여기에 생성
Dockerfile — board-frontend/Dockerfile 완전 해설
# ─── 1단계: 빌드 단계 ───────────────────────────────────────
FROM node:18-alpine AS build
# Node.js 18 + Alpine Linux (가벼운 버전)

WORKDIR /app

COPY package*.json ./
# package.json과 package-lock.json 먼저 복사
# 의존성 캐싱 최적화 → 코드만 바뀌면 npm install 재실행 안 함

RUN npm install
# 의존성 설치

COPY . .
# 나머지 파일 전체 복사

RUN npm run build
# React 프로젝트 빌드
# 결과: /app/build/ 폴더에 정적 파일 생성
# (index.html, main.js, main.css 등)

# ─── 2단계: 실행 단계 ───────────────────────────────────────
FROM nginx:alpine
# Nginx 웹서버로 빌드된 정적 파일 서빙
# Node.js 없이 Nginx만으로 운영 → 훨씬 가볍고 빠름

COPY --from=build /app/build /usr/share/nginx/html
# 빌드된 정적 파일을 Nginx 서빙 폴더로 복사

COPY nginx.conf /etc/nginx/conf.d/default.conf
# 우리 nginx.conf로 Nginx 기본 설정 덮어씀

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
# Nginx를 포그라운드 모드로 실행 (컨테이너용)
nginx.conf — board-frontend/nginx.conf 완전 해설
server {
listen 80;

location / {
root /usr/share/nginx/html;
index index.html;

# React Router 지원
# /board/list 같은 URL로 직접 접근해도
# index.html 반환 → React Router가 처리
try_files $uri $uri/ /index.html;
}

# Spring Boot API 프록시
# /api/ 로 시작하는 요청을 Spring Boot 컨테이너로 전달
location /api/ {
proxy_pass http://springboot:8080;
# springboot = 컨테이너 이름으로 접근!
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
💡 Nginx를 쓰는 이유 2가지
1. 성능: npm start는 개발 전용 서버 → 실제 배포엔 Nginx가 훨씬 빠르고 안정적
2. CORS 해결: /api/ 요청을 Nginx가 Spring Boot로 중계(프록시) → 브라우저는 같은 서버(Nginx)에 요청 → CORS 문제 없음
JavaScript — Docker 환경용 api.js 수정
// 기존 — localhost로 직접 접근
const api = axios.create({
baseURL: 'http://localhost:8080',
});

// Docker 환경 — 빈 문자열 (같은 서버 Nginx로 요청)
const api = axios.create({
baseURL: '',
// api.get('/api/board/list')
// → Nginx가 받아서 → springboot:8080/api/board/list 로 전달
// → CORS 문제도 자동 해결!
});
04 — application.properties 수정
환경변수로 설정 주입 — 로컬 IntelliJ와 Docker 환경 모두 대응
⚙️ 로컬 / Docker 환경 통합 설정
properties — application.properties 수정
# ${환경변수명:기본값} 패턴
# → Docker 실행 시 환경변수 있으면 그걸 사용
# → 로컬 IntelliJ 실행 시 기본값 사용

# DB 연결
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mariadb://localhost:3306/boarddb}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:root}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:1234}
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

# JWT
jwt.secret=${JWT_SECRET:mySecretKeyForJWTTokenGenerationAndValidation2024}
jwt.expiration=86400000

# 관리자 코드
admin.code=${ADMIN_CODE:ADMIN2024}
11a Docker 개념·핵심 요소 📚 전체 맵 11c docker-compose·실행