
Spring Boot 애플리케이션을 Docker 이미지로 빌드하기 위한 Dockerfile 작성.
Dockerfile은 Docker 이미지를 만들기 위한 레시피(설명서)
소스 코드 + Dockerfile → Docker 이미지 → Docker 컨테이너
| 항목 | 설명 |
|---|---|
| 환경 일관성 | 누가, 언제, 어디서 빌드해도 동일한 이미지 |
| 재현 가능성 | 몇 개월 후에도 똑같이 재현 가능 |
| 자동화 | CI/CD 파이프라인에서 자동 빌드 |
| 버전 관리 | Git으로 Dockerfile 변경 이력 추적 |
대안: Spring Boot Buildpacks
# Spring Boot Buildpacks 사용 시
./gradlew bootBuildImage
장점:
단점:
결론: 직접 작성 선택 ✅
FROM gradle:8.5-jdk21
WORKDIR /app
COPY . .
RUN ./gradlew bootJar
CMD ["java", "-jar", "build/libs/*.jar"]
문제점:
# Stage 1: 빌드 (Gradle + JDK)
FROM gradle:8.5-jdk21 AS builder
WORKDIR /app
COPY . .
RUN ./gradlew bootJar
# Stage 2: 실행 (JRE만)
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
CMD ["java", "-jar", "app.jar"]
장점:
| 항목 | 단일 스테이지 | 멀티 스테이지 |
|---|---|---|
| 이미지 크기 | ~1.2GB | ~300MB |
| JDK 포함 | ✅ (불필요) | ❌ (JRE만) |
| Gradle 포함 | ✅ (불필요) | ❌ |
| 보안 | 낮음 | 높음 |
| 빌드 시간 | 동일 | 동일 |
프로젝트 루트에 Dockerfile 생성:
인텔리제이 폴더에 만들면 됩니다!
# 프로젝트 루트로 이동
cd ~/Desktop/DevCource_2
# Dockerfile 생성
touch Dockerfile
# 편집기로 열기
nano Dockerfile
# 또는
code Dockerfile # VS Code 사용 시
# 프로젝트 루트로 이동
cd C:\Users\YourName\Desktop\DevCource_2
# Dockerfile 생성
New-Item -Path . -Name "Dockerfile" -ItemType "file"
# 편집기로 열기
notepad Dockerfile
# 또는
code Dockerfile # VS Code 사용 시
아래 내용을 복사하여 붙여넣기:
# ====================================
# Stage 1: 빌드 스테이지
# ====================================
FROM gradle:8.5-jdk21 AS builder
# 작업 디렉토리 설정
WORKDIR /app
# Gradle 래퍼와 설정 파일 복사 (레이어 캐싱 최적화)
COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts .
COPY settings.gradle.kts .
# 의존성 다운로드 (레이어 캐싱)
# 소스 코드 변경 시에도 의존성은 재다운로드 안 함
RUN ./gradlew dependencies --no-daemon || return 0
# 소스 코드 복사
COPY src src
# 빌드 수행 (테스트 제외)
RUN ./gradlew bootJar -x test --no-daemon
# ====================================
# Stage 2: 실행 스테이지 (경량화)
# ====================================
FROM eclipse-temurin:21-jre-jammy
# 작업 디렉토리 설정
WORKDIR /app
# 비루트 사용자 생성 (보안 강화)
RUN groupadd -r spring && useradd -r -g spring spring
# 빌드된 JAR 파일 복사 (Stage 1에서)
COPY --from=builder /app/build/libs/*.jar app.jar
# uploads 디렉토리 생성 및 권한 설정
RUN mkdir -p /app/uploads && chown -R spring:spring /app
# 사용자 전환 (root → spring)
USER spring:spring
# JVM 메모리 설정 (AWS EC2 t3.micro 기준)
ENV JAVA_OPTS="-Xms512m -Xmx750m -XX:MaxMetaspaceSize=180m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-Djava.security.egd=file:/dev/./urandom"
# 애플리케이션 포트 노출
EXPOSE 8080
# 헬스체크 (Spring Boot Actuator)
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# 컨테이너 시작 시 실행할 명령
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
저장:
Ctrl + X → Y → EnterCtrl + SFROM gradle:8.5-jdk21 AS builder
역할:
왜 JDK 21?
build.gradle.kts에 명시)AS builder란?
COPY --from=builder로 참조WORKDIR /app
역할:
/app으로 설정/app에서 실행왜 /app?
/app 또는 /opt/app에 배치COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts .
COPY settings.gradle.kts .
역할:
왜 순서가 중요한가? (레이어 캐싱)
Docker는 레이어 캐싱 방식으로 동작:
┌────────────────────────────────┐
│ Layer 1: FROM gradle:8.5-jdk21 │ ← 캐시됨
├────────────────────────────────┤
│ Layer 2: COPY gradlew ... │ ← gradlew 변경 시에만 재빌드
├────────────────────────────────┤
│ Layer 3: RUN ./gradlew deps │ ← build.gradle.kts 변경 시에만 재실행
├────────────────────────────────┤
│ Layer 4: COPY src src │ ← 소스 코드 변경 시에만 재복사
├────────────────────────────────┤
│ Layer 5: RUN ./gradlew bootJar │ ← 소스 변경 시에만 재빌드
└────────────────────────────────┘
효과:
잘못된 예시 (비효율):
# 안 좋은 방법
COPY . . # 모든 파일 한 번에 복사
RUN ./gradlew bootJar
# → 소스 한 줄만 바꿔도 전체 재빌드
RUN ./gradlew dependencies --no-daemon || return 0
역할:
--no-daemon이란?
|| return 0이란?
RUN ./gradlew bootJar -x test --no-daemon
역할:
-x test란?
왜 bootJar?
bootJar: 실행 가능한 Fat JAR (모든 의존성 포함)jar: 일반 JAR (실행 불가능)결과물:
/app/build/libs/Back-0.0.1-SNAPSHOT.jar
FROM eclipse-temurin:21-jre-jammy
역할:
왜 eclipse-temurin?
왜 JRE? (JDK 아님)
jammy란?
RUN groupadd -r spring && useradd -r -g spring spring
역할:
spring 생성왜 root로 실행하면 안 되는가?
┌─────────────────────────────────────────┐
│ Docker 컨테이너 (root 사용자) │
│ ↓ 보안 취약점 발견 │
│ ↓ 컨테이너 탈출 (Container Escape) │
│ ↓ │
│ 호스트 시스템 (root 권한 획득) 💥 │
└─────────────────────────────────────────┘
비루트 사용자 사용 시:
┌─────────────────────────────────────────┐
│ Docker 컨테이너 (spring 사용자) │
│ ↓ 보안 취약점 발견 │
│ ↓ 컨테이너 탈출 시도 │
│ ✗ 권한 부족으로 차단 ✅ │
└─────────────────────────────────────────┘
명령어 분석:
groupadd -r spring: spring 그룹 생성 (-r: 시스템 그룹)useradd -r -g spring spring: spring 사용자 생성 및 그룹 추가COPY --from=builder /app/build/libs/*.jar app.jar
역할:
--from=builder란?
builder에서 파일 가져오기/app/build/libs/*.jar → Stage 2의 /app/app.jar왜 다른 파일은 복사 안 하나?
RUN mkdir -p /app/uploads && chown -R spring:spring /app
역할:
spring 사용자에게 소유권 부여왜 필요한가?
/app/uploads에 저장spring 사용자가 쓰기 권한 필요chown이란?
-R: Recursive (하위 디렉토리 포함)USER spring:spring
역할:
spring 사용자로 실행효과:
ENV JAVA_OPTS="-Xms512m -Xmx750m -XX:MaxMetaspaceSize=180m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-Djava.security.egd=file:/dev/./urandom"
역할:
각 옵션 설명:
| 옵션 | 값 | 설명 |
|---|---|---|
-Xms | 512m | 초기 힙 메모리 |
-Xmx | 750m | 최대 힙 메모리 (Docker 980MB 중 75%) |
-XX:MaxMetaspaceSize | 180m | 메타스페이스 최대 크기 |
-XX:+UseG1GC | - | G1 가비지 컬렉터 사용 |
-XX:MaxGCPauseMillis | 200 | GC 일시 정지 목표 시간 (ms) |
-Djava.security.egd | ... | 난수 생성기 설정 (시작 속도 향상) |
왜 G1GC?
EXPOSE 8080
역할:
주의:
docker run -p 8080:8080에서 매핑 필요왜 필요한가?
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
역할:
옵션 설명:
--interval=30s: 30초마다 체크--timeout=3s: 3초 이내 응답 없으면 실패--start-period=60s: 시작 후 60초는 실패해도 OK (앱 시작 시간)--retries=3: 3번 연속 실패 시 unhealthyCMD 설명:
wget --spider: 파일 다운로드 없이 헤더만 확인/actuator/health: Spring Boot Actuator 헬스체크 엔드포인트|| exit 1: 실패 시 exit code 1 반환효과:
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
역할:
왜 sh -c?
$JAVA_OPTS 해석을 위해 셸 필요ENTRYPOINT vs CMD:
ENTRYPOINT: 항상 실행 (변경 어려움)CMD: 기본 명령 (docker run 시 덮어쓰기 가능)결과:
java -Xms512m -Xmx750m ... -jar app.jar
.gitignore와 유사하게 Docker 빌드 시 제외할 파일 지정
없을 때:
COPY . .
# → 모든 파일 복사 (build/, .git/, node_modules/ 등)
# → 빌드 시간 증가, 이미지 크기 증가
있을 때:
# .dockerignore에 명시
build/
.git/
# → 불필요한 파일 제외
# → 빌드 속도 향상, 이미지 크기 감소
cd ~/Desktop/DevCource_2
touch .dockerignore
nano .dockerignore
cd C:\Users\YourName\Desktop\DevCource_2
New-Item -Path . -Name ".dockerignore" -ItemType "file"
notepad .dockerignore
# 빌드 결과물
build/
target/
*.jar
*.war
out/
# Gradle
.gradle/
!gradle/wrapper/gradle-wrapper.jar
# IDE
.idea/
*.iml
*.iws
*.ipr
.vscode/
*.code-workspace
# OS
.DS_Store
Thumbs.db
*.swp
*.swo
*~
# Git
.git/
.gitignore
.gitattributes
# 로그 및 임시 파일
*.log
*.tmp
*.temp
logs/
# 데이터베이스 파일
*.mv.db
*.trace.db
db_*.mv.db
# 업로드 파일
uploads/
# 환경 설정
.env
.env.*
# Docker
Dockerfile
.dockerignore
docker-compose*.yml
# 문서
*.md
!README.md
# 테스트
src/test/
# 기타
node_modules/
npm-debug.log
저장
# 프로젝트 루트로 이동
cd ~/Desktop/DevCource_2
# 이미지 빌드
docker build -t auction-app:latest .
# 또는 빌드 진행 상황 상세히 보기
docker build --progress=plain -t auction-app:latest .
# 프로젝트 루트로 이동
cd C:\Users\YourName\Desktop\DevCource_2
# 이미지 빌드
docker build -t auction-app:latest .
# 또는 빌드 진행 상황 상세히 보기
docker build --progress=plain -t auction-app:latest .
옵션 설명:
-t auction-app:latest: 이미지 이름과 태그 지정.: Dockerfile이 있는 현재 디렉토리--progress=plain: 빌드 로그 상세히 출력 (디버깅용)빌드 시간:
# 이미지 목록 확인
docker images | grep auction-app
# 출력 예시:
# auction-app latest 1d47fc690ca1 2 minutes ago 320MB
확인 사항:
auction-applatest# 이미지 상세 정보
docker history auction-app:latest
# 출력 예시:
# IMAGE CREATED SIZE
# 1d47fc690ca1 2 minutes ago 45MB ENTRYPOINT
# <missing> 2 minutes ago 0B HEALTHCHECK
# <missing> 2 minutes ago 0B EXPOSE 8080
# ...
# 간단히 실행 테스트 (MySQL/Redis 없이)
docker run --rm auction-app:latest java --version
# 출력 예시:
# openjdk version "21.0.1" 2023-10-17
--rm이란?
ERROR: failed to solve: process "/bin/sh -c ./gradlew bootJar" did not complete successfully
원인:
gradlew 파일에 실행 권한 없음해결:
# macOS/Linux
chmod +x gradlew
git add gradlew
git commit -m "Add execute permission to gradlew"
# 다시 빌드
docker build -t auction-app:latest .
ERROR: Could not resolve dependencies
원인:
해결:
# 프록시 설정 (회사 네트워크 등)
docker build \
--build-arg HTTP_PROXY=http://proxy.example.com:8080 \
-t auction-app:latest .
# 또는 Gradle 캐시 삭제 후 재시도
rm -rf ~/.gradle/caches
docker build -t auction-app:latest .
원인:
해결:
# Docker BuildKit 활성화 (더 나은 캐싱)
export DOCKER_BUILDKIT=1 # macOS/Linux
$env:DOCKER_BUILDKIT=1 # Windows PowerShell
# 빌드
docker build -t auction-app:latest .
./gradlew bootBuildImage --imageName=auction-app
장점:
단점:
// build.gradle.kts
plugins {
id("com.google.cloud.tools.jib") version "3.4.0"
}
jib {
to {
image = "auction-app"
}
}
./gradlew jibDockerBuild
장점:
단점:
| 항목 | Dockerfile (직접) | Buildpacks | Jib |
|---|---|---|---|
| 제어력 | ✅ 최고 | ❌ 낮음 | 🟡 중간 |
| 학습 곡선 | 🟡 중간 | ✅ 낮음 | 🟡 중간 |
| 이미지 크기 | ✅ 300MB | ❌ 500MB+ | ✅ 300MB |
| 빌드 속도 | ✅ 빠름 | 🟡 보통 | ✅ 빠름 |
| JVM 튜닝 | ✅ 자유 | ❌ 제한 | 🟡 일부 가능 |
| 헬스체크 | ✅ 가능 | ❌ 제한 | ❌ 제한 |
결론: 학습 목적 + 세밀한 제어 필요 → Dockerfile 직접 작성