Quick Reference
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/htmlbuilder에는 컴파일러·테스트 도구를 두고, final stage에는 실제 실행에 필요한 산출물과 runtime dependency만 COPY --from으로 옮깁니다. 작은 base image나 scratch를 쓰기 전에는 CA 인증서, 시간대 데이터, dynamic library, 실행 사용자 같은 요구를 확인합니다.
스테이지 분리
빌드 도구가 최종 이미지에 남지 않아야 하는 이유
컴파일러, 패키지 매니저, 소스 코드는 빌드할 때만 필요하다. 이것들이 최종 이미지에 포함되면 이미지 크기가 커지고, 불필요한 바이너리가 공격 표면이 된다. 멀티 스테이지 빌드는 빌드 환경과 런타임 환경을 Dockerfile 안에서 분리한다.
# build 스테이지: Go 컴파일러 포함
FROM golang:1.22 AS build
WORKDIR /app
COPY . .
RUN go build -o server .
# runtime 스테이지: 바이너리와 필요한 runtime dependency만 복사
FROM scratch
COPY --from=build /app/server /server
ENTRYPOINT ["/server"]COPY --from으로 이전 스테이지의 아티팩트만 가져오는 방법
COPY --from=스테이지명 또는 COPY --from=숫자로 앞서 완료된 스테이지의 특정 파일이나 디렉터리만 복사한다. 스테이지 전체가 아니라 최종 결과물만 선택적으로 가져올 수 있어 최종 이미지를 최소화할 수 있다.
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt
FROM python:3.12-slim
COPY --from=builder /install /usr/local # 설치된 패키지만 복사
COPY src/ /app/src/
CMD ["python", "/app/src/main.py"]final stage의 실행 요구를 별도로 확인한다
빌드 도구가 포함된 이미지와 산출물만 담은 이미지는 크기가 크게 차이날 수 있다. 다만 scratch나 매우 작은 runtime image에는 CA 인증서, timezone data, 사용자 데이터베이스, dynamic library가 없을 수 있습니다. HTTPS 요청, local time 표시, non-root UID 이름 해석, 동적 링크 여부를 final stage에서 실제로 확인합니다.
# 빌드 전후 크기 비교
docker build -t my-app:single -f Dockerfile.single .
docker build -t my-app:multistage -f Dockerfile.multistage .
docker image ls | grep my-app
# my-app single 1.2GB
# my-app multistage 45MB--target으로 특정 스테이지까지만 빌드하는 패턴
--target을 사용하면 Dockerfile의 특정 스테이지까지만 빌드를 멈출 수 있다. CI에서 테스트 스테이지만 실행하거나, 빌드 결과물을 중간 단계에서 확인할 때 유용하다.
FROM node:22-alpine AS deps
COPY package*.json ./
RUN npm ci
FROM deps AS test
COPY . .
RUN npm test
FROM deps AS build
COPY . .
RUN npm run build
FROM nginx:alpine AS runtime
COPY --from=build /app/dist /usr/share/nginx/html# 테스트 스테이지까지만 실행
docker build --target test -t my-app:test .
# 최종 runtime 스테이지까지 전체 빌드
docker build --target runtime -t my-app:latest .multi-stage와 단일 스테이지는 최종 이미지에 남는 것이 다르다
단일 스테이지는 빌드 도구와 소스, 캐시까지 최종 이미지에 같이 남기기 쉽고, multi-stage는 런타임에 필요한 산출물만 남기는 데 초점이 있습니다. "결과물만 배포할 것인가, 빌드 환경도 같이 남겨도 되는가"를 기준으로 고르면 됩니다.
# 나쁜 단일 스테이지 예
FROM node:22
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["npm", "start"]
# 좋은 multi-stage 예
FROM node:22 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/htmlCOPY 범위 제한
COPY --from=build 범위를 넓게 잡으면 산출물 외에 불필요한 파일까지 최종 이미지에 들어간다. "런타임에 실제로 필요한 파일만" 복사하는 원칙을 끝까지 유지해야 멀티 스테이지 빌드의 효과가 살아난다.
참고 링크
2 sources