Docker の multi-stage build でイメージを一気に軽量化した話

開発用 Dockerfile のまま本番にデプロイしていたイメージを multi-stage build で整理したら、サイズが大幅に減りました。その手順と考え方をまとめます。

🙌 結論から

Docker の multi-stage build を使うと、本番イメージからビルドツールや devDependencies を除いた、すっきりしたイメージを作れます。

実際に仕事で使っていたプロジェクトで試したところ、1.2GB あったイメージが 300MB 程度まで減りました。

イメージが軽くなると、CI のデプロイ時間が短くなるだけでなく、本番環境のセキュリティリスクも下げられます。

本番に余計なツールを持ち込まないという考え方は、今後 Docker を使う上でかなり重要だと感じています。

💡 multi-stage build とは

multi-stage build は、一つの Dockerfile の中に 複数のビルドステージを記述できる Docker の機能です。

たとえば「ビルド用コンテナでコンパイルして、その成果物だけを本番用コンテナにコピーする」という流れを、一つの Dockerfile で完結できます。

以前はこれをやろうとすると、シェルスクリプトや複数の Dockerfile を組み合わせる必要がありました。

Docker 17.05 以降から使える機能なので、現在のほとんどの環境で問題なく使えます。

👀 実際の Dockerfile を書く

Node.js のアプリを例にすると、以下のような構成になります。

Before(single-stage の例)

FROM node:20

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

CMD ["node", "dist/index.js"]

このままだと、node_modules の devDependencies や TypeScript コンパイラなど、本番では不要なものがすべて残ります。

After(multi-stage の例)

# --- ビルドステージ ---
FROM node:20-slim AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# --- 本番ステージ ---
FROM node:20-slim AS runner

WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev

CMD ["node", "dist/index.js"]

COPY --from=builder がポイントで、前のステージで生成した成果物だけを引き継げます。

devDependencies や TypeScript のコンパイラは runner ステージには存在しないので、本番イメージがすっきりします。

✨ ビルドしてみた結果

実際に仕事で使っているプロジェクトに適用してみました。

$ docker images | grep myapp
myapp-before    latest    1.24GB
myapp-after     latest    298MB

1.24GB → 298MB と、4分の1以下まで小さくなりました。

デプロイ時の ECR へのプッシュ時間も体感でかなり速くなり、CI 全体の時間も短縮されています。

さらに、本番コンテナに余計なパッケージが入らないことで、脆弱性スキャンの結果もクリーンになりました。

本番に devDependenciestsc が残っている必要は本来なかったので、「なぜ最初からこうしなかったのか・・・」という感想でした。

一点、ビルドキャッシュの扱いには注意が必要です。

COPY . . をステージの早い段階で書くとキャッシュが効きにくくなるので、依存インストール後にソースをコピーする順番を意識すると、ビルドが速くなります。

👍 まとめ

multi-stage build は、Dockerfile を少し書き換えるだけで 本番イメージを大幅に軽量化できる、費用対効果がかなり高い改善 です。

本番環境のセキュリティを考えると、ビルドツールを本番イメージに含めないのは自然な考え方です。

まだ single-stage で運用しているプロジェクトがあれば、ぜひ一度試してみてほしいです。

イメージが小さくなると、デプロイも速くなり、開発体験もかなり良くなります。