Docker 이미지 크기 줄이기: 빠른 빌드를 위한 실용 가이드
Docker 이미지는 현대적인 클라우드 배포의 근간을 이루지만, 비효율적으로 구성된 이미지는 상당한 마찰을 일으킬 수 있습니다. 지나치게 큰 이미지는 스토리지를 낭비하고, CI/CD 파이프라인을 느리게 하며, 배포 시간(특히 서버리스 환경이나 원격지에서)을 늘리고, 잠재적으로 보안 공격 표면을 확대합니다.
이미지 크기 최적화는 컨테이너 성능 최적화의 중요한 단계입니다. 이 가이드는 다단계 빌드(multi-stage builds), 최소 기본 이미지 선택, 그리고 체계적인 Dockerfile 관행에 중점을 둔 실행 가능한 전문가 기술을 제공하여 훨씬 더 가볍고 빠르며 안전한 컨테이너화된 애플리케이션을 구현하도록 돕습니다.
1. 기초 다지기: 올바른 기본 이미지 선택
이미지 크기에 가장 즉각적인 영향을 미치는 방법은 최소한의 기반을 선택하는 것입니다. 많은 기본 이미지에는 런타임 환경에 전혀 관련 없는 필수 유틸리티, 컴파일러 및 문서가 포함되어 있습니다.
Alpine 또는 Distroless 이미지 사용
Alpine Linux는 표준 최소 선택 사항입니다. 이는 Glibc(Debian/Ubuntu에서 사용) 대신 Musl libc를 기반으로 하며, 일반적으로 메가바이트(MB) 단위의 한 자릿수 크기로 측정되는 기본 이미지를 만듭니다.
| 이미지 유형 | 크기 범위 | 사용 사례 |
|---|---|---|
full/latest (예: node:18) |
500 MB 이상 | 개발, 테스트, 디버깅 |
slim (예: node:18-slim) |
150 - 250 MB | 프로덕션 (Glibc가 필요할 때) |
alpine (예: node:18-alpine) |
50 - 100 MB | 프로덕션 (최대 크기 감소) |
| Distroless | 10 MB 미만 | 고도로 안전한 런타임 전용 프로덕션 환경 |
팁: 애플리케이션이 특정 Glibc 기능에 크게 의존하는 경우 Alpine을 사용하면 런타임 비호환성이 발생할 수 있습니다. Alpine 기반으로 마이그레이션할 때는 항상 철저히 테스트하십시오.
공식 공급업체별 최소 태그 활용
특정 프로그래밍 환경을 사용해야 하는 경우, 항상 공급업체가 공식적으로 유지 관리하는 최소 태그(예: python:3.10-slim, openjdk:17-jdk-alpine)를 우선적으로 사용하십시오. 이 태그들은 호환성을 유지하면서 불필요한 구성 요소를 제거하도록 선별된 것입니다.
2. 강력한 기술: 다단계 빌드(Multi-Stage Builds)
다단계 빌드는 특히 컴파일되거나 의존성이 많은 애플리케이션(예: Java, Go, React/Node 또는 C++)의 이미지 크기를 줄이는 데 가장 효과적인 단일 기술입니다.
이 기술은 빌드 환경(컴파일러, 테스트 도구, 대용량 종속성 패키지가 필요한 환경)을 최종 런타임 환경과 분리합니다.
다단계 빌드가 작동하는 방식
- 1단계 (빌더): 애플리케이션을 컴파일하거나 패키징하기 위해 크고 기능이 풍부한 이미지(예:
golang:latest,node:lts)를 사용합니다. - 2단계 (러너): Alpine, scratch 또는 distroless와 같은 최소 런타임 이미지를 사용합니다.
- 최종 단계는 빌드 도구 및 캐시를 모두 폐기하고 필요한 아티팩트(예: 컴파일된 바이너리, 축소된 에셋)만 빌더 단계에서 선택적으로 복사합니다.
다단계 빌드 예시 (Go)
이 예시에서는 빌더 단계가 폐기되어 scratch(빈 기본 이미지)를 기반으로 하는 극도로 작은 최종 이미지가 생성됩니다.
# 1단계: 빌드 환경
FROM golang:1.21 AS builder
WORKDIR /app
# 소스 코드 복사 및 종속성 다운로드
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# 정적 바이너리 빌드
RUN CGO_ENABLED=0 GOOS=linux go build -a -o /app/server .
# 2단계: 최종 런타임 환경
# 'scratch'는 가능한 가장 작은 기본 이미지입니다
FROM scratch
# 실행 경로 설정 (선택 사항이지만 좋은 습관)
WORKDIR /usr/bin/
# 빌더 단계에서 컴파일된 바이너리만 복사
COPY --from=builder /app/server .
# 애플리케이션 실행 명령 정의
ENTRYPOINT ["/usr/bin/server"]
이 패턴을 구현하면 golang:1.21에서 빌드되었을 때 800 MB였을 수 있는 이미지를 종종 5-10 MB로 줄일 수 있습니다.
3. Dockerfile 최적화 기술
최소 기본 이미지와 다단계 빌드를 사용하더라도, 비효율적인 레이어 관리로 인해 최적화되지 않은 Dockerfile은 불필요한 비대화를 유발할 수 있습니다.
RUN 명령어 결합으로 레이어 최소화
각 RUN 명령어는 새로운 불변 레이어를 생성합니다. 종속성을 설치한 다음 별도의 단계에서 제거하는 경우, 제거 단계는 새 레이어만 추가할 뿐이며, 이전 레이어의 파일은 이미지 기록의 일부로 저장된 채로 남아 이미지 크기에 기여합니다.
항상 종속성 설치와 정리를 단일 RUN 명령어로 결합하고 && 연산자와 줄 연속(\)을 사용하십시오.
비효율적 (두 개의 큰 레이어 생성):
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get remove -y build-essential && rm -rf /var/lib/apt/lists/*
최적화됨 (하나의 작은 레이어 생성):
RUN apt-get update && \n apt-get install -y --no-install-recommends build-essential \n && apt-get clean && rm -rf /var/lib/apt/lists/*
모범 사례:
apt-get install을 사용할 때는 항상--no-install-recommends플래그를 포함하여 불필요한 패키지 설치를 건너뛰고, 동일한RUN명령에서 패키지 목록 및 임시 파일(/var/cache/apt/archives/또는/var/lib/apt/lists/*)을 정리해야 합니다.
.dockerignore 효과적으로 사용하기
.dockerignore 파일은 Docker가 빌드 컨텍스트로 관련 없는 파일(대용량 임시 파일, .git 디렉터리, 개발 로그 또는 광범위한 node_modules 폴더 포함 가능)을 복사하는 것을 방지합니다. 이러한 파일이 최종 이미지에 복사되지 않더라도 빌드 프로세스를 느리게 하고 중간 빌드 레이어를 어지럽힐 수 있습니다.
.dockerignore 예시:
# 개발 파일 및 캐시 무시
.git
.gitignore
.env
# 호스트 머신의 빌드 산출물 무시
node_modules
target/
dist/
# 편집기 파일 무시
*.log
*.bak
ADD보다 COPY 선호
ADD에는 로컬 tar 아카이브 자동 추출 및 원격 URL 가져오기 기능이 있지만, 단순 파일 전송의 경우 COPY가 일반적으로 선호됩니다. ADD가 아카이브를 추출하면 압축 해제된 데이터가 더 큰 레이어 크기에 기여합니다. 아카이브 추출 기능이 명시적으로 필요하지 않은 한 COPY를 고수하십시오.
4. 분석 및 검토
이러한 기술을 구현한 후에는 최대 효율성을 보장하기 위해 결과를 분석하는 것이 중요합니다.
이미지 레이어 검사
docker history 명령을 사용하여 각 단계가 최종 이미지 크기에 정확히 얼마나 기여했는지 확인하십시오. 이는 실수로 비대화를 유발하는 단계를 찾아내는 데 도움이 됩니다.
docker history my-optimized-app
# 출력 예시:
# IMAGE CREATED SIZE COMMENT
# <a> 3 minutes ago 4.8MB COPY --from=builder ...
# <b> 3 weeks ago 4.2MB /bin/sh -c #(nop) WORKDIR /usr/bin/
# <c> 3 weeks ago 3.4MB /bin/sh -c #(nop) CMD [...]
외부 도구 활용
Dive(https://github.com/wagoodman/dive)와 같은 도구는 시각적 인터페이스를 제공하여 각 레이어의 내용을 탐색하고 이미지 크기를 늘리는 중복 파일이나 숨겨진 캐시를 식별할 수 있습니다.
모범 사례 요약
| 기술 | 설명 | 영향 |
|---|---|---|
| 다단계 빌드 | 빌드 종속성(1단계)과 런타임 아티팩트(2단계)를 분리합니다. | 막대한 감소, 일반적으로 80% 이상 |
| 최소 기본 이미지 | alpine, slim 또는 distroless를 사용합니다. |
기준 크기의 상당한 감소 |
| 레이어 결합 | && 및 \를 사용하여 RUN 명령어와 정리 단계를 연결합니다. |
레이어 캐싱 최적화 및 총 레이어 수 감소 |
.dockerignore 사용 |
불필요한 소스 파일, 캐시 및 로그를 빌드 컨텍스트에서 제외합니다. | 빌드 속도 향상, 중간 레이어 축소 |
| 종속성 정리 | 이미지 크기를 부풀리는 잔여 파일을 제거하기 위해 빌드 종속성과 패키지 캐시를 즉시 제거합니다. | 이미지 크기를 증가시키는 잔여 파일 제거 |
다단계 빌드와 세심한 Dockerfile 관리를 체계적으로 적용하면 훨씬 더 작고 빠르며 효율적인 Docker 이미지를 달성하여 배포 시간 개선 및 운영 비용 절감으로 이어질 수 있습니다.