노트

Dockerfile: 레이어 캐시와 멀티 스테이지

Dockerfile: Layer Caching and Multi-Stage Builds

인프라#docker#performance · 연결된 개념 7개

쉽게 말하면

Dockerfile은 층층이 쌓는 케이크 레시피 같아요. 아래층이 그대로면 미리 만든 층을 다시 쓰지만 한 층이 바뀌면 그 위는 전부 새로 만들어서, 자주 바뀌는 소스를 위쪽에 둬야 빌드가 빨라요.

비유가 깨지는 곳 케이크와 달리 완성품에 모든 층을 담을 필요는 없어요. 멀티 스테이지로 빌드 도구는 앞 단계에서만 쓰고 결과물만 최종 이미지로 옮겨요. 다만 비밀값은 한 번 구워 넣으면 레이어에 그대로 남아요.

Dockerfile은 이미지를 만드는 명령을 순서대로 적은 파일이다. 파일시스템을 바꾸는 명령(RUN·COPY·ADD)이 각각 레이어(layer)가 되고, 바뀌지 않은 레이어는 캐시를 재사용한다. 그래서 자주 바뀌는 것을 뒤에 두는 순서가 빌드 속도를 좌우한다.

레이어 캐시 살리기

어떤 레이어가 바뀌면 그 뒤의 레이어는 전부 다시 만든다. 소스를 통째로 복사한 뒤 의존성을 설치하면, 코드 한 줄만 고쳐도 설치를 매번 다시 한다.

FROM node:22-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./   # 의존성 목록만 먼저
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .                              # 소스는 나중에
RUN pnpm build
CMD ["node", "dist/index.js"]

.dockerignore에 node_modules, .git, 빌드 산출물을 넣어 복사 대상과 빌드 컨텍스트(build context)를 줄인다. 비밀 파일(.env)도 여기서 막는다.

멀티 스테이지 빌드

빌드 도구는 빌드 때만 필요하다. 단계를 나눠 마지막 이미지에는 실행에 필요한 결과물만 담는다.

FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
 
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
  • 최종 이미지가 작아져 배포가 빠르고, 공격 표면(attack surface: 셸·컴파일러)도 줄어든다
  • SPA를 nginx로 서빙하는 구성은 nginx로 SPA 배포를 본다

그 밖에

  • docker image history <이미지>로 레이어별 크기를 보고 비대한 단계를 찾는다
  • 베이스 이미지 태그를 고정하고(node:22-alpine), 주기적으로 취약점 스캔을 돌린다
  • 비밀값은 ENV나 COPY로 이미지에 굽지 않는다. 레이어에 영원히 남는다

캐시가 입력 해시로 결과를 재사용한다는 점은 Turborepo의 태스크 캐시와 같은 원리이고, 결국 번들 다이어트처럼 "필요 없는 것을 싣지 않는" 문제다.

출처: Docker 빌드 모범 사례: 빌드 캐시 · 멀티 스테이지 빌드 · .dockerignore

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • Docker Compose

    Docker Compose는 여러 컨테이너로 이뤄진 앱 스택을 YAML 파일 하나로 정의하고, 명령 하나로 띄우고 내리는 도구다. 길어지는 docker run 옵션을 선언형 파일로 옮긴 것이라 보면 된다.

  • 컨테이너 네트워킹

    컨테이너는 각자 격리된 네트워크를 가지므로, 서로 통신하려면 같은 도커 네트워크에 붙인다. 같은 사용자 정의 네트워크(user-defined network) 안에서는 도커 내장 DNS(Domain Name System)가 컨테이너 이름(또는 별칭)을 IP로 풀어 주어, IP 대신 이름으로 접속한다.

  • Next.js 캐시 계층

    Next.js 14~15의 App Router 문서는 캐시를 위치와 수명이 다른 네 계층으로 설명했다. Next.js 16에서 Cache Components를 켜면 캐싱을 'use cache'로 명시하는 모델로 바뀌고 문서도 이 이름들을 쓰지 않는다(use-cache). 그래도 옛 코드와 아티클을 읽으려면 이 그림이 필요하다(2026 기준).

  • 도커 볼륨과 바인드 마운트

    컨테이너의 파일시스템은 이미지 위에 얹힌 얇은 쓰기 레이어라, 컨테이너를 지우면 그 안에 쓴 데이터도 사라진다. 데이터를 남기거나 호스트와 파일을 공유하려면 컨테이너 밖의 저장 공간을 마운트한다. 방법은 named volume과 bind mount 두 가지다.

  • 배럴 파일과 re-export

    배럴 파일(Barrel File)은 여러 모듈의 export를 index.ts 하나에 모아 다시 내보내(re-export) 진입점을 하나로 만드는 파일이다. 쓰는 쪽은 import { Button, Input } from './components'처럼 한 경로에서 가져온다.

보기 옵션