안녕하세요! 👋 오늘은 Docker를 사용하시는 분들이라면 누구나 한 번쯤 고민해봤을 주제, "어떻게 하면 Docker 이미지를 더 작고 효율적으로 만들 수 있을까?" 에 대한 해답을 들고 왔습니다.
느린 빌드 시간, 거대한 이미지 크기, 불필요한 파일로 인한 보안 취약점... 😥 이런 문제들로 골머리를 앓고 계셨다면 오늘 포스팅에 주목해주세요. 특히, Dockerfile 작성의 '치트키'라 불리는 멀티스테이지 빌드(Multi-stage builds) 전략을 중심으로 효율적인 Dockerfile 작성법을 A to Z까지 상세히 알려드릴게요!

🤔 왜 Docker 이미지 크기가 중요한가요?
"일단 돌아가기만 하면 되는 거 아닌가?"라고 생각할 수도 있지만, 이미지 크기는 생각보다 많은 것에 영향을 미칩니다.
- ⚡ 빠른 배포: 이미지 크기가 작을수록 레지스트리(Docker Hub 등)에 push하고 pull하는 속도가 빨라져 CI/CD 파이프라인 전체의 시간이 단축됩니다.
- 💰 비용 절감: 작은 이미지는 레지스트리 저장 공간과 네트워크 트래픽 비용을 줄여줍니다.
- 🔒 보안 강화: 이미지에 빌드 도구나 소스코드 등 실행에 불필요한 파일이 포함되지 않으므로 공격에 노출될 지점(Attack Surface)이 줄어듭니다.
효율적인 Dockerfile 작성은 단순히 '용량 다이어트'를 넘어, 더 빠르고, 저렴하며, 안전한 애플리케이션 환경을 만드는 핵심 첫걸음입니다.
🛠️ 기본 다지기: 효율적인 Dockerfile 작성 전략
멀티스테이지 빌드를 알아보기 전에, 기본적으로 지켜야 할 몇 가지 중요한 전략들이 있습니다. 이것들만 잘 지켜도 이미지 크기를 상당히 줄일 수 있답니다!
1. 가벼운 기반 이미지(Base Image) 선택하기 📦
모든 Docker 이미지는 FROM 명령어로 시작하죠. 이때 어떤 기반 이미지를 선택하느냐가 전체 크기를 좌우합니다.
- 피해야 할 이미지: ubuntu, centos 등 일반적인 OS 이미지는 편리하지만 각종 시스템 유틸리티가 포함되어 있어 매우 큽니다.
- 추천하는 이미지:
- alpine: 초경량 리눅스 배포판으로, 최소한의 기능만 포함하고 있어 매우 인기가 높습니다. (보통 5~10MB 수준)
- slim: 각 언어별 공식 이미지에서 제공하는 경량화 버전입니다. (python:3.9-slim)
- distroless: 'Distro-less(배포판 없음)'라는 이름처럼, 셸이나 패키지 관리자조차 없는, 오직 애플리케이션 실행에 필요한 최소한의 라이브러리만 포함된 구글의 이미지입니다. 보안에 특히 강력합니다.
2. 레이어(Layer) 개수 최소화하기 겹겹이 쌓지 마세요! 🍔
Dockerfile의 RUN, COPY, ADD 명령어는 각각 하나의 새로운 이미지 레이어를 만듭니다. 레이어가 많아질수록 이미지 크기는 커집니다. 여러 RUN 명령어를 && 연산자로 연결하여 하나의 레이어로 합치는 것이 좋습니다.
나쁜 예 👎
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
좋은 예 👍
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Tip: 패키지 설치 후 캐시 파일(rm -rf /var/lib/apt/lists/*)을 같은 RUN 명령어 내에서 삭제해야 해당 레이어에서 실제로 파일이 제거되어 용량이 줄어듭니다.
3. 빌드 캐시(Build Cache) 적극 활용하기 ⚡
Docker는 이미지 빌드 시 각 명령어의 결과를 캐싱해 둡니다. 다음 빌드 시 파일 내용이나 명령어가 변경되지 않았다면, 이전 레이어의 캐시를 그대로 사용하여 빌드 속도를 획기적으로 높입니다.
이 캐시를 잘 활용하려면 변경 빈도가 낮은 명령어부터 높은 순서로 Dockerfile을 작성해야 합니다.
나쁜 예 👎 (소스코드가 바뀔 때마다 매번 의존성 설치)
COPY . . # 소스코드 먼저 복사
RUN npm install # 의존성 설치
CMD ["npm", "start"]
좋은 예 👍 (소스코드가 바뀌어도 캐시된 의존성 레이어 재사용)
WORKDIR /app
COPY package*.json ./ # 1. package.json 먼저 복사 (변경 빈도 낮음)
RUN npm install # 2. 의존성 설치
COPY . . # 3. 나머지 소스코드 복사 (변경 빈도 높음)
CMD ["npm", "start"]
4. .dockerignore 파일 사용은 필수! 🗑️
프로젝트 루트에 .dockerignore 파일을 만들면 .gitignore처럼 Docker 빌드 컨텍스트에 포함되지 않을 파일과 디렉터리를 지정할 수 있습니다. node_modules, build, .git, 각종 로그 파일 등 불필요한 파일이 이미지에 복사되는 것을 막아줍니다.
.git
.vscode
node_modules
npm-debug.log
README.md
🚀 최강의 무기: 멀티스테이지 빌드 (Multi-stage Builds)
자, 이제 오늘의 주인공인 멀티스테이지 빌드에 대해 알아볼 시간입니다!
이 전략은 컴파일이 필요한 언어(Go, Java, C++, Rust 등)나 빌드 과정이 복잡한 프론트엔드 애플리케이션(React, Vue)에서 특히 강력한 힘을 발휘합니다.
멀티스테이지 빌드란?
하나의 Dockerfile 안에 여러 개의 FROM 문을 사용하여 빌드 단계를 여러 개로 나누는 기술입니다.
- 빌드(Build) 스테이지: 첫 번째 스테이지에서는 컴파일러, 빌드 도구, SDK 등 모든 개발 의존성을 포함한 무거운 이미지를 기반으로 소스코드를 컴파일하거나 빌드합니다.
- 최종(Final) 스테이지: 두 번째 스테이지에서는 아주 가벼운 실행 전용 이미지(예: alpine)를 기반으로 시작합니다. 그리고 COPY --from 명령어를 사용하여 이전 빌드 스테이지에서 생성된 결과물(실행 파일, 빌드된 정적 파일 등)만 쏙 가져옵니다.
결과적으로, 최종 이미지에는 무거운 빌드 도구나 소스코드는 전혀 남지 않고, 오직 애플리케이션 실행에 필요한 최소한의 파일만 남게 됩니다!
예시: Go 애플리케이션 빌드하기
멀티스테이지 빌드 적용 전 👎
# Go SDK가 포함된 무거운 이미지를 사용
FROM golang:1.19
WORKDIR /app
# 소스코드와 의존성 파일 복사
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY *.go ./
# 애플리케이션 빌드
RUN go build -o /app/main .
# 이 이미지에는 Go 컴파일러, 소스코드 등 불필요한 파일이 모두 남아있음
# 이미지 크기: 수백 MB ~ 1GB 이상
CMD [ "/app/main" ]
멀티스테이지 빌드 적용 후 👍
# ========== 1. 빌드 스테이지 ==========
# 'builder'라는 이름으로 빌드 스테이지를 정의
FROM golang:1.19 AS builder
WORKDIR /app
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY *.go ./
# Go 애플리케이션을 정적 바이너리로 빌드
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# ========== 2. 최종 스테이지 ==========
# 아주 작은 scratch 이미지나 alpine 이미지를 기반으로 시작
FROM alpine:latest
WORKDIR /root/
# 'builder' 스테이지의 /app/main 파일을 현재 스테이지로 복사
COPY --from=builder /app/main .
# 최종 이미지에는 오직 'main' 실행 파일 하나만 존재!
# 이미지 크기: 10~20MB 수준으로 급격히 감소
CMD ["./main"]
어떤가요? COPY --from=builder 이 한 줄의 마법으로 이미지 크기가 수십 분의 일로 줄어드는 것을 볼 수 있습니다. ✨
이 전략은 Java(JAR 파일만 복사), React(빌드된 정적 파일만 Nginx 이미지로 복사) 등 거의 모든 애플리케이션에 동일하게 적용할 수 있습니다.
✨ 정리하며
오늘 우리는 더 작고, 빠르고, 안전한 Docker 이미지를 만들기 위한 다양한 전략을 살펴봤습니다.
- 가벼운 기반 이미지(alpine, distroless)를 선택하세요.
- &&를 사용해 레이어를 최소화하세요.
- 빌드 캐시를 활용하도록 명령어 순서를 최적화하세요.
- .dockerignore로 불필요한 파일을 제외하세요.
- 그리고 최종 병기, 멀티스테이지 빌드를 사용해 빌드 환경과 실행 환경을 완벽히 분리하세요!
이 원칙들을 여러분의 Dockerfile에 적용해 보세요. 아마 놀랍도록 가벼워진 이미지와 빨라진 빌드 속도를 경험하게 되실 겁니다. Happy Dockering! 🐳
태그: Docker, Dockerfile, Multi stage build, 도커, 도커파일, 멀티스테이지 빌드, 이미지 최적화, DevOps, 컨테이너, CI, CD
'클라우드 > 쿠버네티스' 카테고리의 다른 글
| ⚙️ ConfigMap으로 설정 파일 깔끔하게 분리하고 주입하기 (1) | 2025.09.02 |
|---|---|
| Kubernetes의 심장, 파드(Pod)의 모든 것: 개념부터 생명주기까지 (3) | 2025.09.02 |
| 🚀 12-Factor App: 클라우드 네이티브로 가는 가장 확실한 안내서 (2) | 2025.09.02 |
| 쿠버네티스 Secret, 자체 암호화만으로 충분할까? Vault를 연동하는 진짜 이유 🤫 (8) | 2025.08.30 |
| kubectl의 든든한 조력자, Krew를 소개합니다! 🚀 (1) | 2025.08.30 |