Docker 이미지 크기 줄이기: 더 빠른 빌드를 위한 실용적인 가이드

느린 Docker 배포와 비대해진 이미지에 지치셨나요? 이 전문가 가이드는 컨테이너 크기를 획기적으로 줄이기 위한 실용적이고 실행 가능한 기술을 제공합니다. 빌드 종속성을 최종 런타임과 분리하기 위해 멀티 스테이지 빌드를 활용하는 방법, 스마트 레이어 캐싱을 사용하여 Dockerfile을 최적화하는 방법, 그리고 가능한 가장 작은 기본 이미지(Alpine 등)를 선택하는 방법을 알아보세요. 이러한 전략을 오늘 구현하여 더 빠른 CI/CD 파이프라인, 낮은 스토리지 비용, 향상된 컨테이너 보안을 달성하십시오.

37 조회수

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. 1단계 (빌더): 애플리케이션을 컴파일하거나 패키징하기 위해 크고 기능이 풍부한 이미지(예: golang:latest, node:lts)를 사용합니다.
  2. 2단계 (러너): Alpine, scratch 또는 distroless와 같은 최소 런타임 이미지를 사용합니다.
  3. 최종 단계는 빌드 도구 및 캐시를 모두 폐기하고 필요한 아티팩트(예: 컴파일된 바이너리, 축소된 에셋)만 빌더 단계에서 선택적으로 복사합니다.

다단계 빌드 예시 (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 이미지를 달성하여 배포 시간 개선 및 운영 비용 절감으로 이어질 수 있습니다.