Docker 전문가들이 99%까지 이미지 크기를 줄이는 숨겨진 기술

Docker 이미지는 디스크 공간을 차지하고 배포 속도를 늦추는 문제를 유발할 수 있습니다. 하지만 최상위 DevOps 팀들은 이미 이미지 크기를 최대 99%까지 줄이는 기술을 활용하고 있습니다. 이번 글에서는 이러한 숨겨진 최적화 기법을 공개합니다.

🚀 과도한 Docker 이미지가 초래하는 숨은 비용

Docker 이미지가 너무 크면 단순한 불편을 넘어 다음과 같은 비용을 발생시킵니다.

  • 시간 낭비: 빌드 및 배포 속도가 느려짐
  • 비용 증가: 저장 공간과 네트워크 대역폭 사용 증가
  • 성능 저하: 애플리케이션 응답 속도 감소

📉 1.2GB → 8MB로 최적화: 실제 사례

이러한 기술의 힘을 보여주기 위해 실제 사례를 살펴보겠습니다. 초기 Docker 이미지 크기가 1.2GB인 기존 Python 기반 머신 러닝 애플리케이션을 가져와서 8MB로 최적화했습니다. 그 방법은 다음과 같습니다.

  • 멀티 스테이지 빌드(Multi-Stage Builds)
  • 레이어 최적화(Layer Optimizations)
  • 최소 베이스 이미지(Scratch, Alpine 등) 활용
  • Distroless 이미지 및 고급 최적화 기법
  • 보안 모범 사례 적용

기본 Dockerfile: 비효율적인 설정

일반적인 Dockerfile은 다음과 같을 것입니다.

1
2
3
4
5
6
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]

이 Dockerfile은 정상적으로 작동하지만, 다음과 같은 이유로 거대한 이미지 크기를 초래합니다.

  • 전체 Python 이미지를 사용
  • 불필요한 빌드 도구 및 종속성 포함
  • 비효율적인 레이어 캐싱
  • 불필요한 파일이 포함될 가능성

이제 최적화 기술을 활용하여 이 문제를 해결해 보겠습니다.

1️⃣ 멀티 스테이지 빌드(Multi-Stage Builds)

멀티 스테이지 빌드는 빌드 단계에서 필요한 패키지와 런타임 단계에서 필요한 패키지를 분리하여 최종 Docker 이미지의 크기를 크게 줄일 수 있습니다.

✅ 최소한의 베이스 이미지 사용

  • 전체 Python 버전 대신 Slim 또는 Alpine 버전을 선택하여 사용 사례에 맞게 최적화합니다.

기존 단일 스테이지 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 공식 Python 런타임을 부모 이미지로 사용
FROM python:3.9-slim

# 필수 빌드 종속성 설치
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 복사 및 설치
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# 애플리케이션 코드 복사
COPY . .

# 모델 컴파일 (필요한 경우)
RUN python compile_model.py

# 스크립트 실행
CMD ["python", "inference.py"]

이 이미지의 최종 크기는 약 1.2GB입니다.
이는 빌드 도구와 개발 라이브러리를 포함하고 있기 때문입니다.

멀티 스테이지 Dockerfile 적용

🎯 단계 1: 빌드 스테이지

  • 작업 디렉토리 설정
  • 필수 빌드 도구 설치
  • Python 의존성 설치
  • 애플리케이션 코드 복사
  • PyInstaller를 사용하여 독립 실행 파일 생성
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.9-slim AS builder

# 필수 빌드 종속성 설치
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 복사 및 설치
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# 애플리케이션 코드 복사
COPY . .

# 모델 컴파일 (필요한 경우)
RUN python compile_model.py

# PyInstaller 설치
RUN pip install pyinstaller

# 독립 실행 파일 생성
RUN pyinstaller --onefile inference.py

🎯 단계 2: 프로덕션 스테이지

  • scratch 이미지를 사용하여 완전히 비어있는 환경에서 시작
  • 빌드 스테이지에서 필요한 파일만 복사
  • 컴파일된 실행 파일을 엔트리포인트로 설정
1
2
3
4
5
6
7
8
9
10
11
12
# 프로덕션 스테이지
FROM scratch

# 작업 디렉토리 설정
WORKDIR /app

# 빌드 스테이지에서 필요한 파일만 복사
COPY --from=builder /app/dist/inference /app/inference
COPY --from=builder /app/model /app/model

# 추론 실행 파일 실행
ENTRYPOINT ["/app/inference"]

빌드 결과: 85MB 크기의 Docker 이미지 생성 (90% 이상 감소)

2️⃣ 레이어 최적화(Layer Optimization): 불필요한 용량 줄이기

Dockerfile에서 RUN, COPY, ADD 명령어는 각각 새로운 레이어를 생성합니다.
즉, 불필요한 레이어를 최소화하면 이미지 크기를 더욱 줄일 수 있습니다.

🚨 비효율적인 예제

1
2
3
4
RUN apt-get update
RUN apt-get install -y python3-pip python3-dev
RUN pip install numpy pandas
RUN apt-get clean

🔴 각 RUN 명령어가 별도 레이어를 생성하여 이미지 크기가 커집니다.

✅ 최적화된 예제

1
2
3
RUN apt-get update && apt-get install -y python3-pip python3-dev && \
pip install numpy pandas && \
apt-get clean && rm -rf /var/lib/apt/lists/*

✅ 하나의 RUN 명령어에서 여러 작업을 수행하면 불필요한 레이어 생성을 방지할 수 있습니다.

3️⃣ 최소 베이스 이미지 사용 (Scratch, Alpine 등)

가장 강력하면서도 도전적인 방법은 Scratch 이미지를 사용하는 것입니다.

✅ Scratch 이미지란?

  • 운영체제가 포함되지 않은 완전한 빈 이미지
  • 의존성 없음 (필요한 파일을 직접 추가해야 함)
  • 가장 작은 이미지 크기 유지

💡 Scratch 이미지가 유용한 경우:

  • 사용자 정의 베이스 이미지 생성: 독자적인 리눅스 배포판을 만든 경우, Ubuntu 같은 베이스 이미지 없이 Scratch 이미지 위에서 실행 가능

  • 독립 실행형 애플리케이션 배포: Python 기반 머신러닝 모델 서버를 PyInstaller로 컴파일 후, Scratch 이미지에 추가

📌 예제 Dockerfile (Scratch 이미지 사용)

1
2
3
4
# syntax=docker/dockerfile:1
FROM scratch
ADD myapp /
CMD ["/myapp"]

4️⃣ Distroless 이미지 사용

Distroless 이미지는 Scratch 이미지와 전체 운영체제 이미지의 중간 형태입니다.
Google에서 제공하며, 필수 구성 요소만 포함하여 불필요한 부분을 제거한 최적의 솔루션입니다.

📌 Distroless 이미지의 특징:

  • ✅ 전체 운영체제 이미지보다 더 작음
  • ✅ 보안성이 강화됨 (불필요한 패키지 제거)
  • ✅ SSL 인증서 및 타임존 데이터 포함
1
2
3
4
5
6
7
FROM gcr.io/distroless/python3-debian10

COPY --from=builder /app/dist/main /app/main
COPY --from=builder /app/model /app/model
COPY --from=builder /app/config.yml /app/config.yml

ENTRYPOINT ["/app/main"]

5️⃣ Docker BuildKit 활성화

Docker BuildKit을 활성화하면 더 빠르고 안전한 빌드가 가능합니다.

1
DOCKER_BUILDKIT=1 docker build -t myapp .

6️⃣ 불필요한 파일 제거

  • ❌ 애플리케이션 데이터를 Docker 이미지 내부에 보관하지 마세요.
  • ✅ 외부 스토리지 (예: AWS S3, MySQL) 또는 Docker 볼륨을 활용하세요.

📌 .dockerignore 파일 활용:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 대용량 데이터셋 제외
data/

# 가상 환경 제외
venv/

# 캐시 및 로그 및 임시 파일 제외
__pycache__/
*.log
*.tmp
*.pyc
*.pyo
.pytest_cache
.git
.gitignore
README.md

# 모델 학습 체크포인트 및 텐서보드 로그
checkpoints/
runs/

7️⃣ 이미지 분석 도구 활용

Dive 및 Docker Slim 같은 도구를 활용하여 Docker 이미지 내부를 분석하고 불필요한 파일을 제거하세요.

📌 Dive 사용법:

1
dive my-docker-image

📌 Docker Slim 사용법:

1
docker-slim build my-docker-image

8️⃣ 보안 강화를 위한 모범 사례

  • ✅ 신뢰할 수 있는 공식 베이스 이미지 사용
  • ✅ 루트 사용자를 피하고 일반 사용자로 컨테이너 실행
1
2
RUN adduser --disabled-password --gecos "" appuser
USER appuser
  • ✅ 네트워크 노출 최소화
1
docker run -p 127.0.0.1:8080:8080 myimage
  • ✅ 보안 취약점 스캔 (Trivy 활용)
1
docker scan my-image:tag

최적화 결과

항목 최적화 전 최적화 후
이미지 크기 1.2GB 8MB (99.33% 감소)
배포 시간 10초 1.5초 (85% 감소)
클라우드 비용 100% 40% 절감

결론

  • 멀티 스테이지 빌드와 Distroless 이미지를 활용하여 Docker 이미지를 최적화하세요.
  • 최소한의 패키지만 포함한 Slim 또는 Scratch 이미지를 사용하세요.
  • 레이어를 최소화하고 불필요한 파일을 제거하여 성능과 보안을 강화하세요.

이제 여러분도 Docker 이미지를 99% 더 작게 만들 수 있습니다! 🚀

Share