マルチステージビルドで軽量化
ビルドツールや開発用依存を最終イメージに残さないための、マルチステージビルドの考え方と典型パターンをまとめます。
マルチステージビルドとは
マルチステージビルドは、1 つの Dockerfile に 複数の FROM を書き、ステージ間で成果物だけを受け渡す仕組みです。
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
1 つ目のステージ (builder) にはビルドに必要なツール一式が入りますが、最終イメージは nginx:alpine にビルド成果物だけをコピーした小さいものになります。ビルド時に必要なものと実行時に必要なものを分離するのが基本発想です。
Go: コンパイル言語の定番パターン
コンパイル言語ではマルチステージの効果が最も分かりやすく出ます。
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/app /app
USER 65532:65532
ENTRYPOINT ["/app"]
CGO_ENABLED=0 で静的リンクにし、Go ツールチェーンを含む 1 GB 超のベースから、数十 MB の distroless イメージにコピーするだけで済みます。
USER 65532:65532 は distroless の nonroot ユーザーで、最低限の安全設定として付けておきます。
Node.js / Python: 依存解決と実行の分離
スクリプト言語でも、ネイティブ拡張のビルドツールを最終イメージから外せます。
# Node.js 例
FROM node:20 AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20 AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
Python の場合は wheel を一度ビルドして、実行ステージでは pip install せず wheel だけコピーするパターンも有効です。
native 拡張(gcc、build-essential、libpq-dev など)を実行イメージから取り除けます。
特定のステージまでビルドする
docker build --target <stage> で、途中のステージだけビルドできます。
CI でテスト専用ステージを持たせて、「テストに通ったらさらに runtime までビルド」のような使い方に便利です。
FROM node:20 AS deps
# ...
FROM deps AS test
COPY . .
RUN npm test
FROM deps AS build
COPY . .
RUN npm run build
FROM node:20-slim AS runtime
# ...
docker build --target test -t my-app:test .
docker build --target runtime -t my-app:latest .
効果を計測する
マルチステージ化の効果はサイズで確認します。
docker build -t my-app:single -f Dockerfile.single .
docker build -t my-app:multi -f Dockerfile.multi .
docker image ls my-app
# レイヤー構成の確認
docker history my-app:multi
典型的には、Go のサーバーアプリで 1 GB → 20 MB、Node.js で 1.2 GB → 200 MB 程度まで縮むことが多いです。
サイズが小さくなると、pull 時間・コールドスタート時間・脆弱性スキャンの対象面も縮みます。
