Featured image of post Dockerマルチステージビルドによるイメージサイズ削減手法 Featured image of post Dockerマルチステージビルドによるイメージサイズ削減手法

Dockerマルチステージビルドによるイメージサイズ削減手法

Dockerマルチステージビルドを使ったイメージサイズ削減手法を徹底解説。開発環境と本番実行環境を分離し、不要なビルド依存関係や中間成果物を排除することでデプロイ時間を短縮し、セキュリティリスクを低減する実践的なベストプラクティスを紹介します。

はじめに

Dockerコンテナのイメージサイズを小さく保つことは、デプロイ時間の短縮、サーバーのストレージ節約、そして脆弱性を減らすセキュリティ向上(アタックサーフェスの削減)において非常に重要です。

しかし、単純にベースイメージを作成しようとすると、ビルドに必要なコンパイラ(gccなど)、パッケージマネージャ、あるいはソースコードのテストライブラリなどが最終的なコンテナイメージに混入し、イメージサイズが数百MB〜数GBに膨れ上がってしまいがちです。

この問題をエレガントに解決する機能が、Dockerの マルチステージビルド(Multi-stage builds) です。本記事では、マルチステージビルドの基本原則と、実際のNode.jsプロジェクトを例にした具体的な削減ステップを解説します。


1. マルチステージビルドとは?

マルチステージビルドとは、1つの Dockerfile 内で複数の FROM 指示子を使用し、ビルド処理をいくつかの「ステージ」に分ける手法です。

あるステージでビルドしたコンパイル済みの実行バイナリや依存ファイルを、次のステージへ「コピー」して引き継ぐことができます。これにより、ビルド用ステージで使った巨大な開発用パッケージ(コンパイラ、SDK、ソースファイルなど)をすべて切り捨て、実行に必要な最小限のファイルだけを本番用ステージに残すことができます。

【ビルドステージ】
・ベースイメージ: node (大)
・開発ライブラリ、ソースコード
・ビルドコマンド実行 (npm run build)
          ▼ (必要な成果物だけをコピー)
【本番ステージ】
・ベースイメージ: alpine / scratch (極小)
・ビルド成果物 (dist/ や html)のみ
・実行コマンド

2. 従来型とマルチステージビルドの比較

実際に、Node.js(TypeScript)アプリケーションをコンテナ化する例で、従来型とマルチステージ型を比較してみましょう。

避けるべき従来型の Dockerfile

# 開発・ビルドに必要なツールが含まれる重いイメージ
FROM node:20

WORKDIR /app

# 依存パッケージのインストール
COPY package*.json ./
RUN npm install

# ソースコードのコピーとビルド(TypeScript -> JS)
COPY . .
RUN npm run build

# アプリケーションの起動
EXPOSE 3000
CMD ["node", "dist/index.js"]
  • 課題: このイメージには、ビルド後に不要になるTypeScriptコンパイラ(devDependencies)、ソースファイル(.tsファイル)、そして node_modules 内の膨大な開発用ツールキットがすべて含まれたままになります。結果として、イメージサイズは簡単に 800MB〜1GB を超えてしまいます。

改善されたマルチステージビルドの Dockerfile

# -----------------------------------------------
# ステージ1: ビルド用環境 (builder)
# -----------------------------------------------
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
# 開発用ライブラリも含めてすべてインストール
RUN npm ci

COPY . .
# TypeScriptをJavaScriptにコンパイル
RUN npm run build

# 本番環境用に、不要なdevDependenciesを削除して再インストール
RUN npm prune --production

# -----------------------------------------------
# ステージ2: 本番実行用環境 (runner)
# -----------------------------------------------
# 実行に必要な最小限の軽量イメージ (alpine)
FROM node:20-alpine AS runner

WORKDIR /app
ENV NODE_ENV=production

# ステージ1 (builder) からビルド成果物と本番用のnode_modulesだけをコピー
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

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

3. なぜ劇的にサイズが減るのか?(ビルド結果)

このマルチステージビルドを適用することで、以下のような効果が得られます。

  • 開発依存(devDependencies)の完全排除: npm prune --production により、TypeScriptなどのビルド時にしか使わない重いツール群が除外され、本番用モジュールだけが残ります。
  • alpineベースイメージの採用: Debianベースの重い node イメージの代わりに、Alpine Linuxベースの軽量イメージを使用することで、ベースOSの容量を削減します。
  • 結果: イメージサイズが約 900MB から、なんと 150MB以下 まで激減します。

4. さらにサイズを下げるためのテクニック

.dockerignore ファイルの作成

Dockerfileと同じ階層に .dockerignore ファイルを作成し、ビルドコンテキストに不要なファイル(ローカルの node_modules.git、環境変数ファイル .env など)が含まれないようにします。

# .dockerignore の例
node_modules
.git
.github
npm-debug.log
dist
.env

キャッシュの最適化

COPY package*.json ./ をソースコードのコピー(COPY . .)より前に行うことで、ソースコードに変更があっても package.json が変わっていなければ npm install のキャッシュ層が再利用され、ビルド時間が短縮されます。

まとめ

Dockerのマルチステージビルドは、コンテナ運用の効率を最大化するための必須テクニックです。

  1. AS builder のようにビルドステージに名前をつけて処理を実行する
  2. COPY --from=builder で実行に必要な最小限のファイルのみを本番ステージに引き継ぐ
  3. OSのベースには軽量な alpinedistroless イメージを選ぶ

デプロイパイプラインの高速化と安全性の向上のために、ぜひマルチステージビルドを導入してみてください。