“이제와서 시작하는 Docker 마스터하기” Docker 이미지의 크기와 빌드 시간은 배포 속도와 비용에 직접적인 영향을 미칩니다. 이번 편에서는 이미지를 최적화하는 다양한 기법을 실습과 함께 알아보겠습니다.
이미지 크기가 중요한 이유
- 빠른 배포: 작은 이미지는 다운로드가 빠릅니다
- 저장 공간 절약: 레지스트리와 호스트의 디스크 공간 절약
- 보안: 불필요한 패키지가 없어 공격 표면이 줄어듭니다
- 성능: 컨테이너 시작 시간이 단축됩니다
현재 이미지 분석하기
1. 이미지 크기 확인
1
2
3
4
5
6
7
8
| # 이미지 크기 확인
docker images
# 상세 레이어 정보
docker history <image-name>
# 레이어별 크기 확인
docker history <image-name> --no-trunc --format "table {{.CreatedBy}}\t{{.Size}}"
|
2. Dive 도구로 분석
1
2
3
4
| # Dive 설치 및 실행
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest <image-name>
|
기본 최적화 기법
1. 적절한 베이스 이미지 선택
1
2
3
4
5
6
7
8
9
10
11
12
| # 나쁜 예: 큰 베이스 이미지
FROM ubuntu:latest
RUN apt-get update && apt-get install -y python3
# 좋은 예: 특화된 이미지
FROM python:3.11-slim
# 더 좋은 예: Alpine 기반
FROM python:3.11-alpine
# 최고의 예: Distroless
FROM gcr.io/distroless/python3-debian11
|
2. 레이어 최소화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 나쁜 예: 많은 레이어
FROM alpine:3.18
RUN apk update
RUN apk add python3
RUN apk add py3-pip
RUN pip install flask
RUN pip install gunicorn
# 좋은 예: 하나의 레이어로 통합
FROM alpine:3.18
RUN apk update && \
apk add --no-cache python3 py3-pip && \
pip install --no-cache-dir flask gunicorn && \
rm -rf /var/cache/apk/*
|
3. 불필요한 파일 제거
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 패키지 캐시 제거
RUN apt-get update && \
apt-get install -y python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# pip 캐시 제거
RUN pip install --no-cache-dir -r requirements.txt
# 빌드 도구 제거
RUN apk add --no-cache --virtual .build-deps gcc musl-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del .build-deps
|
멀티 스테이지 빌드
1. Node.js 애플리케이션 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| # 빌드 스테이지
FROM node:18-alpine AS builder
WORKDIR /app
# 의존성만 먼저 복사 (캐시 활용)
COPY package*.json ./
RUN npm ci --only=production
# 개발 의존성 포함 설치 (빌드용)
COPY . .
RUN npm ci && npm run build
# 프로덕션 스테이지
FROM node:18-alpine
# dumb-init 설치 (PID 1 문제 해결)
RUN apk add --no-cache dumb-init
# 사용자 생성
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# 빌드 결과물만 복사
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --chown=nodejs:nodejs package.json ./
USER nodejs
EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]
|
2. Go 애플리케이션 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 빌드 스테이지
FROM golang:1.21-alpine AS builder
RUN apk add --no-cache ca-certificates git
WORKDIR /app
# 의존성 먼저 다운로드 (캐시 활용)
COPY go.mod go.sum ./
RUN go mod download
# 소스 코드 복사 및 빌드
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o app .
# 실행 스테이지 (scratch)
FROM scratch
# CA 인증서 복사 (HTTPS 요청용)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 바이너리만 복사
COPY --from=builder /app/app /app
ENTRYPOINT ["/app"]
|
3. Java 애플리케이션 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| # 빌드 스테이지
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
# 의존성 캐싱을 위한 별도 복사
COPY pom.xml .
RUN mvn dependency:go-offline
# 소스 코드 복사 및 빌드
COPY src ./src
RUN mvn package -DskipTests
# JRE 추출 (JLink)
RUN jlink \
--add-modules java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output /javaruntime
# 실행 스테이지
FROM debian:bullseye-slim
ENV JAVA_HOME=/opt/java/openjdk
ENV PATH="${JAVA_HOME}/bin:${PATH}"
# JRE 복사
COPY --from=builder /javaruntime $JAVA_HOME
# 애플리케이션 JAR 복사
COPY --from=builder /app/target/*.jar app.jar
# 사용자 생성
RUN useradd -m -u 1001 appuser
USER appuser
ENTRYPOINT ["java", "-jar", "/app.jar"]
|
언어별 최적화 팁
Python 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 멀티 스테이지 빌드
FROM python:3.11-slim AS builder
# 빌드 의존성 설치
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 가상 환경 생성
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 런타임 스테이지
FROM python:3.11-slim
# 가상 환경 복사
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY . .
CMD ["python", "app.py"]
|
Node.js 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| FROM node:18-alpine
# node-prune 설치 (불필요한 파일 제거)
RUN apk add --no-cache curl && \
curl -sf https://gobinaries.com/tj/node-prune | sh
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
node-prune && \
rm -rf /usr/local/lib/node_modules/npm && \
rm -rf ~/.npm
COPY . .
CMD ["node", "index.js"]
|
고급 최적화 기법
1. BuildKit 캐시 마운트
1
2
3
4
5
6
| # syntax=docker/dockerfile:1
FROM python:3.11-slim
# 캐시 마운트 사용
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
|
2. 멀티 플랫폼 빌드
1
2
3
4
5
6
7
8
| # BuildKit 활성화
docker buildx create --use
# 멀티 플랫폼 빌드
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myapp:latest \
--push .
|
3. 조건부 복사
1
2
3
4
5
6
7
8
| # BuildKit 문법
# syntax=docker/dockerfile:1
FROM alpine
# 조건부 복사
ARG BUILD_ENV=production
COPY configs/${BUILD_ENV}.conf /etc/app/config.conf
|
이미지 크기 비교
실제 예시: Python Flask 앱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 최적화 전 (1.2GB)
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
# 최적화 후 (150MB)
FROM python:3.11-slim AS builder
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.11-slim
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
|
보안을 위한 최적화
1. 최소 권한 사용자
1
2
3
4
5
6
7
8
9
10
11
12
| FROM alpine:3.18
# 사용자 생성
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# 필요한 디렉토리 생성 및 권한 설정
RUN mkdir -p /app && \
chown -R appuser:appgroup /app
USER appuser
WORKDIR /app
|
2. 읽기 전용 파일시스템
1
2
3
4
5
6
7
| FROM alpine:3.18
# 임시 디렉토리는 쓰기 가능하게
VOLUME ["/tmp", "/var/tmp"]
# 앱 실행
USER nobody
|
자동화된 최적화
1. CI/CD에서 이미지 스캔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # .github/workflows/docker.yml
name: Docker Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t myapp:$ .
- name: Scan image size
run: |
SIZE=$(docker images myapp:${{ github.sha }} --format "{{.Size}}")
echo "Image size: $SIZE"
# 크기 제한 체크 (500MB)
SIZE_MB=$(docker images myapp:${{ github.sha }} --format "{{.Size}}" | sed 's/MB//')
if (( $(echo "$SIZE_MB > 500" | bc -l) )); then
echo "Image too large!"
exit 1
fi
|
2. 이미지 최적화 도구
1
2
3
4
5
6
7
| # docker-slim 사용
docker run -it --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
dslim/docker-slim build myapp:latest
# 결과 확인
docker images | grep myapp
|
실습: 이미지 크기 줄이기
최적화 전후 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #!/bin/bash
# compare-images.sh
echo "=== 이미지 크기 비교 ==="
# 최적화 전 빌드
docker build -f Dockerfile.original -t myapp:original .
ORIGINAL_SIZE=$(docker images myapp:original --format "{{.Size}}")
# 최적화 후 빌드
docker build -f Dockerfile.optimized -t myapp:optimized .
OPTIMIZED_SIZE=$(docker images myapp:optimized --format "{{.Size}}")
echo "Original: $ORIGINAL_SIZE"
echo "Optimized: $OPTIMIZED_SIZE"
# 레이어 분석
echo -e "\n=== 레이어 분석 ==="
docker history myapp:optimized
|
체크리스트
이미지 최적화 체크리스트
- 적절한 베이스 이미지 선택 (Alpine, Distroless)
- 멀티 스테이지 빌드 사용
- 레이어 수 최소화
- 패키지 캐시 정리
- 불필요한 파일 제거 (.git, tests, docs)
- 빌드 도구 제거
- 정적 링크 바이너리 사용 (Go, Rust)
- 압축 가능한 파일 압축
- .dockerignore 파일 활용
- 캐시 마운트 사용
모범 사례 정리
1. 캐싱 최대화
1
2
3
4
| # 변경이 적은 것부터 복사
COPY package*.json ./
RUN npm ci
COPY . .
|
2. 레이어 재사용
1
2
3
4
5
6
7
8
9
| # 베이스 이미지를 공통으로 사용
ARG BASE_IMAGE=node:18-alpine
FROM ${BASE_IMAGE} AS base
FROM base AS development
# 개발 설정
FROM base AS production
# 프로덕션 설정
|
3. 빌드 시간 단축
1
2
3
4
5
6
7
8
9
10
11
| # 병렬 빌드
# syntax=docker/dockerfile:1
FROM alpine AS build1
RUN command1
FROM alpine AS build2
RUN command2
FROM alpine
COPY --from=build1 /output1 .
COPY --from=build2 /output2 .
|
마무리
Docker 이미지 최적화는 지속적인 과정입니다. 작은 이미지는 빠른 배포, 낮은 비용, 향상된 보안을 가져다줍니다. 프로젝트의 특성에 맞는 최적화 전략을 선택하고 지속적으로 개선해 나가세요. 다음 편에서는 Docker와 CI/CD 통합에 대해 알아보겠습니다.
다음 편 예고
- CI/CD 파이프라인 구축
- 자동화된 빌드와 배포
- 테스트 자동화
- GitOps 실습
자동화된 배포 파이프라인을 구축해봅시다! 🚀
📚 Docker 마스터하기 시리즈
🐳 기초편 (입문자용 - 5편)
- Docker란 무엇인가?
- Docker 설치 및 환경 설정
- 첫 번째 컨테이너 실행하기
- Docker 이미지 이해하기
- Dockerfile 작성하기
💼 실전편 (중급자용 - 6편)
- Docker 네트워크 기초
- Docker 볼륨과 데이터 관리
- Docker Compose 입문
- 멀티 컨테이너 애플리케이션
- Docker Hub 활용하기
- Docker 보안 베스트 프랙티스
🚀 고급편 (전문가용 - 9편)
- Docker 로그와 모니터링
- Docker로 Node.js 애플리케이션 배포
- Docker로 Python 애플리케이션 배포
- Docker로 데이터베이스 운영
- Docker 이미지 최적화 ← 현재 글
- Docker와 CI/CD
- Docker Swarm 기초
- 문제 해결과 트러블슈팅
- Docker 생태계와 미래