도커 이미지 크기 줄이기, 멀티스테이지와 distroless

2026. 10. 8. 13:00·IT
728x90
반응형

도커 이미지가 1GB를 넘어가면 배포할 때마다 기다리게 된다. 이미지를 올리고 내려받는 시간이 길어지고, 오토스케일링으로 새 노드가 뜰 때 서비스 투입이 늦어진다. 장애 상황에서 롤백이 느린 것도 같은 이유다. 이 글은 이미지 크기를 줄이는 방법을 효과가 큰 순서대로 정리한다. 멀티스테이지 빌드 하나로 대개 절반 이하가 되고, distroless까지 가면 수십 MB 단위가 된다.

왜 이미지가 커지는가

크기의 대부분은 세 곳에서 온다.

베이스 이미지가 첫 번째다. node:20은 1GB 가까이 되지만 node:20-slim은 200MB 안팎, node:20-alpine은 더 작다. 차이는 안에 들어 있는 것이다. 전체 이미지에는 컴파일러, 빌드 도구, 각종 유틸리티가 다 들어 있다.

빌드 도구와 중간 산출물이 두 번째다. 컴파일러, 개발 의존성, 캐시 디렉터리가 최종 이미지에 남는다. 실행할 때는 하나도 필요 없는 것들이다.

레이어의 특성이 세 번째다. 도커 이미지는 레이어가 쌓인 구조이고, 한 번 추가된 파일은 나중 레이어에서 지워도 이미지 크기가 줄지 않는다. 앞 레이어에 그대로 남아 있기 때문이다.

RUN apt-get install -y build-essential   # 300MB 추가
RUN apt-get remove -y build-essential    # 크기는 그대로. 오히려 늘어남

지우려면 같은 RUN 안에서 지워야 한다.

RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
 && make all \
 && apt-get purge -y build-essential \
 && rm -rf /var/lib/apt/lists/*

멀티스테이지 빌드가 가장 큰 효과를 낸다

멀티스테이지는 빌드 환경과 실행 환경을 아예 분리한다. 빌드용 스테이지에서 필요한 것을 다 깔아 컴파일하고, 최종 스테이지에는 결과물만 복사한다. 빌드 스테이지는 배포되지 않고 버려진다.

# 1단계: 빌드 (무겁게 써도 상관없다)
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev

# 2단계: 실행 (결과물만)
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]

효과는 사례마다 다르지만 방향은 일정하다. 멀티스테이지를 쓰지 않았을 때 1.43GB였던 이미지가 746MB로 줄었다는 기록이 있고, 베이스 이미지 교체와 .dockerignore를 함께 적용해 322MB까지 내린 사례도 있다.

Go나 Rust처럼 정적 바이너리를 만드는 언어라면 효과가 더 극적이다. 최종 스테이지에 바이너리 하나만 넣으면 된다.

.dockerignore 를 빠뜨리는 경우가 많다

COPY . .을 쓰면 빌드 컨텍스트 전체가 들어간다. .git 디렉터리, node_modules, 로컬 로그, 테스트 픽스처, 스크린샷까지 전부다. .git만으로도 수백 MB가 되는 저장소가 흔하다.

.git
node_modules
dist
coverage
*.log
.env*
**/__pycache__

.env를 넣는 것은 크기 때문만이 아니다. 이미지에 비밀 값이 들어가면 그 이미지를 가진 누구나 꺼내 볼 수 있다. 레이어를 풀면 그대로 나온다. 이건 크기 문제가 아니라 사고다.

distroless 는 무엇을 빼는가

distroless는 애플리케이션과 그 런타임 의존성만 남기고 나머지를 전부 뺀 베이스 이미지다. 셸이 없고, 패키지 관리자가 없고, ls도 cat도 없다.

FROM golang:1.26 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/server

FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]

크기 이득도 크지만 보안 이득이 더 크다. 공격자가 컨테이너 안에서 코드를 실행하게 되더라도 쓸 도구가 없다. 셸이 없으니 리버스 셸을 띄우기 어렵고, curl이나 wget이 없으니 추가 페이로드를 받아오기 어렵다. 취약점 스캔 결과도 크게 줄어든다. 깔린 패키지가 없으면 알려진 취약점도 없다.

대가는 디버깅이다. kubectl exec로 들어가도 셸이 없어서 아무것도 못 한다. 이때는 임시 디버그 컨테이너를 붙이는 방식(kubectl debug)을 쓴다. 운영 절차에 이 방법을 미리 넣어두지 않으면 장애 상황에서 당황하게 된다.

캐시를 살리는 레이어 순서

크기만큼 중요한 것이 빌드 시간이다. 도커는 레이어 단위로 캐시하고, 한 레이어가 바뀌면 그 뒤 레이어는 전부 다시 만든다. 그래서 자주 바뀌는 것을 뒤에 둬야 한다.

COPY package*.json ./     # 거의 안 바뀜
RUN npm ci                # 위가 안 바뀌면 캐시 적중
COPY . .                  # 코드는 매번 바뀜
RUN npm run build

순서를 뒤집어 COPY . .을 먼저 하면, 코드 한 줄만 고쳐도 npm ci가 매번 다시 돈다. 이 차이가 빌드 시간의 대부분을 좌우한다.

어디까지 줄일 것인가

여기서 균형을 짚고 싶다. 나는 무조건 작게 만드는 것을 목표로 삼지 않는다.

alpine 계열이 작다고 무조건 좋은 것은 아니다. alpine은 musl libc를 쓰기 때문에 glibc를 전제로 빌드된 네이티브 모듈에서 문제가 생긴다. 파이썬 패키지 중 미리 빌드된 휠을 못 쓰고 소스에서 컴파일하게 되는 경우가 있는데, 그러면 빌드가 몇 분씩 걸리고 이미지도 오히려 커진다. 크기를 줄이려다 빌드 시간을 잃는다.

기준을 정하면 이렇다. 멀티스테이지와 .dockerignore는 예외 없이 적용한다. 비용이 거의 없고 효과가 확실하다. 베이스 이미지 교체는 테스트하고 적용한다. slim 계열까지는 대체로 안전하고, alpine은 확인이 필요하다. distroless는 보안 요구가 있을 때 간다. 디버깅 절차를 함께 마련한 뒤에 적용한다.

그리고 줄이기 전에 지금 크기가 실제로 문제인지 확인하는 편이 좋다. 배포 시간이 얼마나 걸리는지, 그중 이미지 전송이 몇 초인지 재보면 된다. 레지스트리가 같은 리전에 있고 노드에 캐시가 있으면 1GB 이미지도 체감이 크지 않다. 측정 없이 최적화하면 효과 없는 일에 시간을 쓰게 된다.

참고 자료

  • 도커 이미지 크기 줄이기 — 레이어의 진실, 멀티스테이지, 베이스 이미지의 실제 비용 — 레이어 동작 원리와 최적화 순서
  • Docker 이미지 용량 줄이기 — 멀티 스테이지와 EFS — 멀티스테이지 적용 전후 크기 비교
  • 컨테이너 최적화(Distroless) 가이드 — CNCF.PRO — distroless 의 구조와 보안 이점
728x90
반응형

'IT' 카테고리의 다른 글

Git 브랜치 전략, rebase와 merge 중 무엇을 쓸까  (0) 2026.10.10
npm 공급망 공격 방어, 락파일부터 쿨다운까지  (0) 2026.10.09
Node vs Deno vs Bun, 2026년 런타임 선택 기준  (0) 2026.10.07
PostgreSQL 19 REPACK, 새벽 점검이 줄어든다  (0) 2026.10.06
쿠버네티스 1.35, 파드 재시작 없이 자원 조정  (0) 2026.10.05
'IT' 카테고리의 다른 글
  • Git 브랜치 전략, rebase와 merge 중 무엇을 쓸까
  • npm 공급망 공격 방어, 락파일부터 쿨다운까지
  • Node vs Deno vs Bun, 2026년 런타임 선택 기준
  • PostgreSQL 19 REPACK, 새벽 점검이 줄어든다
밍글링글링
밍글링글링
mingling - 밍글링, 밍글밍글링. 코드와 어우러지다. IT/ 프로그래밍/소스
    반응형
    250x250
  • 밍글링글링
    mingling
    밍글링글링
  • 전체
    오늘
    어제
    • 밍글링글링 (451) N
      • Flutter (2)
      • 일상생활 (8)
        • 리뷰 (1)
        • 생활정보 (4)
        • 맛집 (0)
        • 여행 (0)
        • 모든정보 (3)
      • JAVA (126)
        • 개념 (6)
        • 예제 (115)
        • Exception (2)
      • C (1)
        • C (1)
        • C++ (0)
        • C# (0)
      • JS (29)
        • JavaScript (18)
        • JQuery (5)
        • AJax (0)
        • NODE.JS (6)
        • Angular.JS 2.0 (0)
      • WEB (87)
        • HTML (6)
        • CSS (61)
        • JSP (20)
        • JSTL (0)
      • FrameWork (8)
        • Spring (8)
        • BootStrap (0)
        • MyBATIS (0)
        • JUnit (0)
      • 외부 라이브러리 (5)
      • 공유 소스 관리 (5)
        • Git (5)
        • SVN (0)
      • 빅데이터 프로그래밍 (37)
        • Python (37)
        • R Programming (0)
      • DB (7)
        • ORACLE (0)
        • MySql (6)
      • Development Tools (7)
        • StarUML (0)
        • eXERD (0)
        • Eclipse (4)
      • SKILL (6)
        • Migration (0)
        • Security (6)
      • MicroSoft (0)
        • Excel (0)
        • Word (0)
      • Android (0)
      • Server (21)
        • Ubuntu (5)
        • Linux (15)
      • IOS (0)
      • XML (0)
      • 미디어 (0)
      • 공지사항 (3)
      • NETWORK (1)
      • 게임 (4)
        • 피파 (1)
        • 리니지M (0)
        • 배틀그라운드 (1)
        • 듀랑고 (2)
      • 세상 이슈 (6)
      • 일렉트론 (0)
      • 대회 소식 (4)
      • 업무 (2)
      • Express, Vue (6)
      • docker (11)
      • svelte (3)
      • 블록체인 (1)
      • IT (54) N
      • Rust (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    리눅스 설치
    자바 exception
    AI 반도체
    vue cli
    nginx ssl 설정
    servlet class
    vscode
    클론코딩
    오류
    docker
    SSL 인증서
    제미나이
    러스트
    rust linux
    nginx
    mysql db
    ssl 인증서 발급
    AI 코딩 에이전트
    자바 배열
    ubuntu
    Extension Bisect
    React Compiler
    css float
    에디터 팁
    gitlab 설치
    자바 클래스
    java casting
    css tb
    nginx ssl 적용
    lang rust
    Rust lang
    자바 for문
    jsp include
    티스토리 자동화
    vue 설치
    svelte
    API 설계
    자바 생성자
    css perspective
    proxy pass
    자바 객체 지향
    Java Array
    프런트엔드
    VS Code 팁
    브라우저 자동화
    css transition
    css table
    jsp parameter
    spring java
    Node
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
밍글링글링
도커 이미지 크기 줄이기, 멀티스테이지와 distroless
상단으로

티스토리툴바