[Docker] 멀티 스테이지 빌드 - 이미지 크기 획기적으로 줄이는 최적화 전략

2025. 6. 9. 19:26·Docker
728x90
반응형
반응형

안녕하세요.

오늘은 Dockerfile을 기반으로 이미지를 Build할 때, 최적화하는 방법을 알려드리려고 합니다.

 

클라우드 네이티브 시대의 핵심 기술인 Docker는 개발 및 배포 프로세스를 혁신하며 수많은 개발팀의 필수 도구로 자리 잡았습니다. 하지만 Docker 이미지를 빌드하다 보면 의도치 않게 거대한 이미지 크기, 느린 빌드 속도, 그리고 잠재적인 보안 취약점이라는 문제에 직면할 수 있습니다. 이는 배포 시간을 늘리고, 스토리지 비용을 증가시키며, 애플리케이션의 안정성을 저해하는 요인이 됩니다.이러한 문제들을 해결하고 더 작고, 빠르며, 안전한 Docker 이미지를 만드는 데 있어 가장 효과적인 방법이 바로 '멀티 스테이지 빌드(Multi-Stage Build)'입니다. 

왜 멀티 스테이지 빌드인가?

우리가 일반적인 방식으로 Docker 이미지를 빌드할 때, 대부분의 애플리케이션은 빌드 과정에서 수많은 개발 도구, 컴파일러, 임시 파일 등을 필요로 합니다. 예를 들어, 자바 애플리케이션을 빌드하려면 JDK와 Gradle/Maven이 필요하고 Node.js 애플리케이션은 Node.js 런타임과 npm/yarn이 필요합니다. 문제는 이러한 빌드 도구들이 최종 애플리케이션 실행에는 전혀 필요 없음에도 불구하고 빌드된 이미지에 그대로 포함된다는 점입니다.

주요 문제점

✅ 거대한 이미지 크기
불필요한 빌드 도구와 개발 라이브러리가 최종 이미지에 포함되어 이미지 크기가 불필요하게 커집니다. 이는 저장 공간을 많이 차지하고, 네트워크를 통한 이미지 다운로드 시간을 늘려 배포 지연을 초래합니다.
✅ 느린 빌드 및 배포 속도
이미지 크기가 크면 CI/CD 파이프라인에서 이미지를 빌드하고 푸시하며 풀하는 전체 과정이 길어집니다.
✅ 보안 취약점 증가
개발 환경에 필요한 수많은 패키지들이 최종 이미지에 남아있으면, 그 안에 알려지지 않은 또는 알려진 보안 취약점이 존재할 가능성이 높아져 공격 표면을 넓힙니다.

멀티 스테이지 빌드로 인해 해결된 문제들

멀티 스테이지 빌드는 이러한 문제들을 해결하기 위해 Docker 17.05 버전부터 도입된 강력한 기능입니다. 이 방식은 빌드 환경과 최종 런타임 환경을 완벽하게 분리하여, 최종 이미지에는 오직 애플리케이션 실행에 필요한 최소한의 요소만을 포함시킵니다. 

✅ 이미지 크기 대폭 감소
불필요한 파일이 제거되어 이미지 크기가 혁신적으로 줄어듭니다.
✅ 빌드 및 배포 속도 향상
작은 이미지는 더 빠르게 빌드되고 배포됩니다.
✅ 보안 강화
공격 대상이 줄어들어 잠재적인 보안 위협이 감소합니다.
✅ CI/CD 파이프라인 효율 증대
전반적인 파이프라인의 속도와 안정성이 향상됩니다.

멀티 스테이지 빌드의 기본 원리 이해

멀티 스테이지 빌드의 핵심은 하나의 Dockerfile 안에서 여러 개의 'FROM' 명령어를 사용하는 것입니다. 'FROM' 명령어는 독립적인 빌드 스테이지(Stage)를 생성하며 스테이지는 이전 스테이지와 별도의 컨텍스트를 가집니다. 또한 스테이지에는 `AS` 키워드를 사용하여 이름을 부여할 수 있으며, 이 이름은 다음 스테이지에서 특정 스테이지의 결과물을 참조할 때 사용됩니다.

기본적인 흐름

첫 번째 스테이지 (빌드 스테이지)
애플리케이션을 빌드하는 데 필요한 모든 개발 도구와 소스 코드를 포함합니다. 여기서 컴파일, 의존성 설치, 번들링 등의 작업이 이루어집니다.
두 번째 스테이지 (최종 스테이지)
애플리케이션을 실행하는 데 필요한 최소한의 런타임 환경만을 포함합니다. 그리고 첫 번째 스테이지에서 빌드된 최종 아티팩트(예: 실행 가능한 JAR 파일, 컴파일된 JS 번들)만을 `COPY --from` 명령어를 사용하여 가져옵니다.

멀티 스테이지 빌드의 예

다음은 간단한 Node.js 웹 애플리케이션을 멀티 스테이지 빌드하는 Dockerfile입니다.

# Stage 1: 'builder' 스테이지 - 애플리케이션 빌드 환경
FROM node:18-alpine AS builder

WORKDIR /app

# package.json 및 package-lock.json 복사 후 의존성 설치
COPY package.json package-lock.json ./
RUN npm ci

# 나머지 소스 코드 복사
COPY . .

# 빌드 작업 (예: TypeScript 컴파일, 웹팩 번들링 등)
RUN npm run build

# Stage 2: 'production' 스테이지 - 최종 실행 환경
FROM node:18-alpine

WORKDIR /app

# 'builder' 스테이지에서 생성된 'node_modules' 디렉토리와 최종 애플리케이션 코드를 복사
# 여기서는 불필요한 개발 의존성이나 빌드 도구는 제외됩니다.
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app ./

# 애플리케이션이 사용할 포트
EXPOSE 3000

# 애플리케이션 실행 명령어
CMD ["node", "index.js"]

멀티 스테이지 빌드 속도를 위한 최적화 전략

멀티 스테이지 빌드의 기본 원리를 이해했다면, 이제 이를 더욱 효과적으로 활용하여 이미지 크기를 최소화하고 빌드 속도를 극대화하는 실전 전략을 살펴보겠습니다.

불필요한 파일 최소화 ('.dockerignore' 활용)

문제점

Docker 빌드 과정은 Docker의 빌드 컨텍스트(즉, 'Dockerfile'이 있는 디렉토리의 모든 파일)를 Docker 데몬으로 전송하는 것부터 시작됩니다. 이때 빌드에 전혀 필요 없는 파일들(예: '.git' 디렉토리, IDE 설정 파일, 로컬 캐시 파일, 'node_modules' 등)이 포함되면 빌드 컨텍스트 전송 시간이 길어지고 빌드 캐싱 효율이 떨어질 수 있습니다.

해결

'.dockerignore' 파일은 Git의 '.gitignore'와 유사하게 빌드 시 특정 파일이나 디렉토리를 빌드 컨텍스트에서 제외하도록 지정하는 역할을 합니다. 이를 통해 불필요한 파일 전송을 막고, 빌드 속도를 향상하며, 최종 이미지에 의도치 않은 파일이 포함되는 것을 방지할 수 있습니다.

# .dockerignore 예시
node_modules/
.git/
.vscode/
npm-debug.log
Dockerfile
.dockerignore
README.md
*.env
dist/ # 빌드 스테이지에서 생성되므로 최종 스테이지에서 필요 없음

빌드 캐시 효율적 활용

Docker는 'Dockerfile' 명령어를 개별적인 레이어로 빌드하고, 이 레이어들을 캐시합니다. 특정 명령어가 변경되지 않았다면 Docker는 캐시된 레이어를 재사용하여 빌드 시간을 단축합니다. 이 캐시 메커니즘을 최대한 활용하는 것이 빌드 속도 최적화의 핵심입니다.

변경 빈도가 낮은 레이어를 위에 배치

Dockerfile내에서 변경될 가능성이 낮은 명령어를 상단에 배치하고, 자주 변경되는 명령어를 하단에 배치하는 것이 좋습니다. 예를 들어, 애플리케이션 의존성 설치 ('npm install', 'pip install')는 소스 코드 변경보다 빈번하게 일어나지 않으므로 소스 코드를 복사하는 'COPY . .' 명령보다 먼저 실행되도록 구성해야 합니다.
# 나쁜 예: 소스 코드 변경 시마다 npm install이 다시 실행됨
COPY . .
RUN npm install

# 좋은 예: package.json만 변경될 때만 npm install이 다시 실행됨
COPY package.json package-lock.json ./
RUN npm ci # 의존성 설치
COPY . .   # 소스 코드 복사

레이어 최소화 및 불필요한 파일 제거

'RUN' 명령을 '&&'로 연결하여 여러 명령어를 하나의 레이어로 묶는 것이 좋습니다. 각 'RUN' 명령은 새로운 레이어를 생성하며, 한 번 추가된 레이어의 파일은 이후 삭제하더라도 이미지 크기에는 반영되지 않고 '더티(dirty)' 레이어로 남게 됩니다. 따라서 패키지 설치 후 캐시를 정리하는 등의 작업을 한 번에 수행하여 최종 이미지 크기를 줄여야 합니다.
# 나쁜 예: 세 개의 레이어가 생성되고, apt 캐시가 남아 이미지 크기에 영향을 줌
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 --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*
 
--no-install-recommends 옵션은 Ubuntu/Debian 기반 이미지에서 불필요한 권장 패키지 설치를 막아 이미지 크기를 더욱 줄여줍니다.

빌드 아티팩트 관리 및 보안 강화

루트(Root) 사용자 방지

Docker 컨테이너 내에서 애플리케이션을 루트(Root) 권한으로 실행하는 것은 보안에 매우 취약합니다. 'USER' 명령어를 사용하여 컨테이너 내부의 루트가 아닌 사용자를 생성하고 그 권한으로 애플리케이션을 실행해야 합니다.
FROM node:18-alpine

# appuser 그룹과 사용자 생성 (시스템 사용자)
RUN addgroup --system appuser && adduser --system --ingroup appuser appuser

WORKDIR /app
COPY --from=builder --chown=appuser:appuser /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appuser /app .

USER appuser # 이후 명령은 appuser 권한으로 실행됨
EXPOSE 3000
CMD ["node", "server.js"]

정기적인 이미지 스캔

최종 빌드된 Docker 이미지에 알려진 보안 취약점이 없는지 주기적으로 스캔하는 것이 좋습니다.

마치며

멀티 스테이지 빌드는 단순한 기능이 아니라, 개발 및 운영 파이프라인 전체의 효율성과 보안성을 끌어올리는 강력한 도구입니다. 더 이상 거대한 이미지 크기와 느린 배포 속도로 고민하지 말고 Dockerfile에 멀티 스테이지 빌드를 적용하여 더 작고 빠르며 안전한 컨테이너 이미지를 통해 최적화를 해보시기 바랍니다.

 

긴 글 읽어주셔서 감사합니다.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'Docker' 카테고리의 다른 글

[Docker] Slack의 강력한 대안 - Mattermost 셀프 호스팅 구축 가이드  (2) 2025.06.11
[Docker] AdguardVPN Linux버전 사용기 (AdguardVPNCLI) + qBittorrent #2  (0) 2025.02.17
[Docker] AdguardVPN Linux버전 사용기 (AdguardVPNCLI) + qBittorrent #1  (0) 2025.01.21
[Docker/arm64] Wordpress 알아보기 2편 - MiniO 적용방법  (0) 2025.01.20
[Docker/arm64] Wordpress 알아보기 1편 - Memcached 적용방법  (1) 2025.01.20
'Docker' 카테고리의 다른 글
  • [Docker] Slack의 강력한 대안 - Mattermost 셀프 호스팅 구축 가이드
  • [Docker] AdguardVPN Linux버전 사용기 (AdguardVPNCLI) + qBittorrent #2
  • [Docker] AdguardVPN Linux버전 사용기 (AdguardVPNCLI) + qBittorrent #1
  • [Docker/arm64] Wordpress 알아보기 2편 - MiniO 적용방법
Str@wBerry
Str@wBerry
Poki's Second Brain
  • Str@wBerry
    Poki's World
    Str@wBerry
  • 전체
    오늘
    어제
    • 분류 전체보기 (37)
      • 지식창고 (2)
      • Javascript (10)
      • Docker (13)
      • Web (5)
      • Linux (5)
      • Windows (2)
  • 블로그 메뉴

    • HOME
  • 링크

    • 공지사항

      • 인기 글

        • 태그

          JavaScript
          조절
          애드가드
          leak
          AdGuard
          rendering
          해결
          MinIO
          렌더링
          도커
          필수
          Memory
          VPN
          워드프레스
          자바스크립트
          자동
          Video
          함수
          과정
          용량
          차이
          Linux
          완료
          Wordpress
          이해
          docker
          Process
          누수
          백업
          부족
        • 최근 댓글

          • 최근 글

            • hELLO· Designed By정상우.v4.10.3
            Str@wBerry
            [Docker] 멀티 스테이지 빌드 - 이미지 크기 획기적으로 줄이는 최적화 전략
            상단으로

            티스토리툴바