목차
- WHY — 왜 Dockerfile을 알아야 하는가
- Dockerfile 핵심 문법
- 레이어 캐시 전략
- CMD vs ENTRYPOINT
- Spring Boot 앱 이미지 만들기
- Multi-stage Build
- .dockerignore
- 전체 요약
1. WHY — 왜 Dockerfile을 알아야 하는가
Part 1에서 redis:7.2를 pull해서 컨테이너로 띄웠다. 그건 누군가 만들어둔 이미지를 가져다 쓴 것이다.
아리 팬 커뮤니티의 카드 뽑기 서비스, 채팅 서비스를 K8s에 올리려면? 내 Spring Boot 앱을 직접 이미지로 만들어야 한다. Dockerfile을 모르면 그게 불가능하다.
Dockerfile (설계도)
│
│ docker build
▼
Image (결과물)
│
│ docker run
▼
Container (실행)
2. Dockerfile 핵심 문법
명령어 역할
| FROM | Base 이미지 지정 |
| RUN | 이미지 빌드 시 실행 (레이어 생성) |
| COPY | 로컬 파일을 이미지 안으로 복사 |
| ENV | 환경변수 설정 |
| ARG | 빌드 시점에만 쓰는 변수 |
| EXPOSE | 컨테이너가 사용할 포트 선언 |
| WORKDIR | 작업 디렉토리 설정 |
| CMD | 컨테이너 시작 시 실행할 기본 명령어 (덮어쓰기 가능) |
| ENTRYPOINT | 고정 실행 명령어 (덮어쓰기 불가) |
기본 Dockerfile 예시
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
WORKDIR /app
COPY ari.txt .
ENV DOG_NAME=Ari
ENV DOG_AGE=11
CMD ["cat", "/app/ari.txt"]
빌드 및 실행:
docker build -t ari-hello .
docker run --rm ari-hello
# 아리는 11살 믹스견, 최고의 강아지입니다!
3. 레이어 캐시 전략
Dockerfile 명령어 한 줄이 레이어 하나다. 특정 레이어가 바뀌면 그 아래 레이어는 전부 캐시가 무효화된다.
나쁜 예 — 매번 패키지를 재설치
FROM ubuntu:22.04
COPY ari.txt . ← 자주 바뀜
RUN apt-get install curl ← 매번 재실행됨 (27초 낭비)
좋은 예 — 캐시 최대 활용
FROM ubuntu:22.04
RUN apt-get install curl ← 잘 안 바뀜 → CACHED (0초)
COPY ari.txt . ← 자주 바뀜 → 여기만 재실행
원칙: 자주 바뀌는 것은 아래에, 잘 안 바뀌는 것은 위에 Spring Boot 앱 기준으로는 라이브러리 설치는 위에, 내 코드 COPY는 아래에 배치한다.
실제로 확인한 결과:
# 첫 빌드
[2/4] RUN apt-get install curl 27.5s
# 두 번째 빌드 (코드만 변경)
[2/4] CACHED RUN apt-get install curl 0.0s ← 캐시 재사용
4. CMD vs ENTRYPOINT
둘 다 컨테이너 시작 시 실행되는 명령어지만 동작 방식이 다르다.
CMD ENTRYPOINT
| 역할 | 기본 명령어 | 고정 명령어 |
| docker run 시 덮어쓰기 | 가능 | 불가 |
| 실무 사용 | 기본값 지정 | 항상 같은 실행 파일 |
ENTRYPOINT ["cat"]
CMD ["/app/ari.txt"]
# CMD 기본값 사용
docker run --rm ari-hello
# 아리는 11살 믹스견!
# CMD를 덮어써서 다른 파일 출력
docker run --rm ari-hello /etc/hostname
# 148efbe34f9f
docker run 이미지명 뒤에 붙이는 인자는 CMD를 대체한다. ENTRYPOINT ["cat"]는 항상 고정이고, 인자만 바뀐다.
Spring Boot 앱 기준 실무 패턴
ENTRYPOINT ["java", "-jar", "app.jar"]java -jar는 항상 고정이므로 ENTRYPOINT로 지정한다.
5. Spring Boot 앱 이미지 만들기 (단순 방식)
로컬에서 gradle로 빌드 후 jar를 COPY하는 방식이다.
./gradlew build
build/libs/
├── ari-0.0.1-SNAPSHOT-plain.jar ← 의존성 없는 jar (사용 안 함)
└── ari-0.0.1-SNAPSHOT.jar ← 실행 가능한 fat jar (이걸 씀)
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY build/libs/ari-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
docker build -t ari-app .
docker run -d --name ari-app -p 8080:8080 ari-app
curl http://localhost:8080/health
# Ari is alive! 🐶
6. Multi-stage Build
단순 방식의 문제점
로컬에서 gradle로 빌드하면:
- 로컬에 Java, Gradle이 설치되어 있어야 함
- CI/CD 서버에도 동일한 환경 구성 필요
- 빌드 환경이 표준화되지 않음
Multi-stage Build 구조
Stage 1 (builder)
└── gradle:8-jdk17 (1.24GB)
소스코드 빌드 → jar 생성
│
│ jar 파일만 꺼냄
▼
Stage 2 (runner)
└── eclipse-temurin:17-jre-jammy
jar만 실행
최종 이미지 = 401MB (빌드 환경 1.24GB는 버려짐)
# Stage 1: 빌드
FROM gradle:8-jdk17 AS builder
WORKDIR /app
COPY . .
RUN gradle build --no-daemon -x test
# Stage 2: 실행
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY --from=builder /app/build/libs/ari-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
JDK vs JRE
JDK JRE
| 포함 내용 | 컴파일러 + 실행 환경 | 실행 환경만 |
| 사용 위치 | Stage 1 (빌드) | Stage 2 (운영) |
| 크기 | 큼 | 작음 |
| 보안 | 공격 면적 큼 | 공격 면적 작음 |
운영 환경에서는 JRE만 포함하는 것이 표준이다. 컴파일이 필요 없고, 이미지 크기와 보안 측면에서 모두 유리하다.
Multi-stage Build의 진짜 가치
개발자 git push
│
▼
GitHub Actions
│
├── docker build
│ (Dockerfile 안에서 gradle build → jar 생성)
│
├── docker push (Registry)
│
└── kubectl apply (K8s 배포)
Dockerfile 하나에 빌드 환경까지 포함되어 있으므로, 개발자는 소스코드만 push하면 이후는 전부 자동이다. 로컬에 Java, Gradle이 없어도 빌드 가능하다.
7. .dockerignore
docker build 시 빌드 컨텍스트에 포함되는 파일을 제한한다. 불필요한 파일이 포함되면 빌드가 느려지고 이미지가 커진다.
# .dockerignore
.gradle
build
.gitignore
*.md
HELP.md
.git
적용 전후 빌드 컨텍스트 크기 비교:
# .dockerignore 적용 전
transferring context: 456.08kB
# .dockerignore 적용 후
transferring context: 5.06kB ← 약 90배 감소
build/ 폴더(컴파일된 클래스, jar)와 .gradle/(캐시)가 제외된 덕분이다.
8. 전체 요약
Dockerfile 핵심 원칙
├── 레이어 캐시: 자주 바뀌는 것은 아래에, 안 바뀌는 것은 위에
├── ENTRYPOINT: 고정 실행 파일 지정 (java -jar)
├── CMD: 기본 인자 지정 (docker run 시 덮어쓰기 가능)
└── .dockerignore: 불필요한 파일 제외
Multi-stage Build 핵심 원칙
├── Stage 1: JDK + 빌드 도구 (최종 이미지에 포함 안 됨)
├── Stage 2: JRE만 (운영 환경 표준)
├── 소스코드가 최종 이미지에 없음 → 보안
└── 로컬 환경 없이 빌드 가능 → CI/CD 연동
최종 Dockerfile
# Stage 1: 빌드
FROM gradle:8-jdk17 AS builder
WORKDIR /app
COPY . .
RUN gradle build --no-daemon -x test
# Stage 2: 실행
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
'Infra & Network' 카테고리의 다른 글
| [Docker | Part 4] Volume + Network + Registry (0) | 2026.04.30 |
|---|---|
| [Docker | Part 3] Docker Compose (0) | 2026.04.30 |
| [Docker | Part 1] Docker 핵심 구조 이해 (0) | 2026.04.23 |
| [Redis HA | Part 1] Redis Master-Replica + Sentinel 구성 (1) | 2026.04.22 |
| Azure Container Apps와 Private VNet 통합하기 (1) | 2025.07.28 |