はじめに
この記事では、Dockerのマルチステージビルドを使って、軽量なイメージを作成する方法を解説します。
マルチステージビルドとは
マルチステージビルドとは、一つのDockerfileでビルドと実行のプロセスを分ける手法です。
これを使うことで、最終的なDockerイメージのサイズを縮小することが可能になります。
マルチステージビルドの仕組み
マルチステージビルドは、ビルドステージと実行ステージの2つのステージを作成します。
ビルドステージ
コンパイルや依存パッケージのインストールなどのビルドに必要な作業をここで行います。ビルドが完了すると、このステージは破棄されます。
実行ステージ
ビルドステージで生成された実行ファイルだけを実行ステージのイメージにコピーして実行します。
すでに実行ファイルが生成されているため、このステージでは軽量なイメージのみを使用することが可能になります。
マルチステージビルドの例
この例では、マルチステージビルドを活用し、最終的なDockerイメージのサイズを縮小しています。
/usr/app
にはTypeScriptのソースコードがあると仮定します。
# ビルドステージ
FROM node AS build
WORKDIR /usr/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 実行ステージ
FROM node
WORKDIR /usr/app
COPY package*.json ./
RUN npm install --production
COPY --from=build /usr/app/dist ./dist
EXPOSE 4000
CMD node dist/src/index.js
上記例では、
① ビルドステージにて/usr/app
の内容をビルド
② ①でビルドされた./usr/app/dist
のファイルのみをコピーし実行
のマルチステージによる処理が行われています。
--from=build
のbuild
は、ビルドステージにてFROM
で指定したエイリアスです。
実行ステージの--from=build
にて、ビルドしたイメージを使用する指示を行なっています。
このbuild
部分は好きな名称に設定が可能です。
エイリアスを指定しない場合は、--from=build
の代わりに--from=0
のようにステージ番号で参照することも可能です。
比較例
実際に上記のDockerfileと、マルチステージにしていない(シングルステージの)Dockerfileでのイメージのサイズを比較します。
以下のようなdependencies
とwebDependencies
を使用します。
{
// ...省略
"dependencies": {
"express": "^5.1.0"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^24.2.1",
"babel-loader": "^10.0.0",
"cypress": "^14.5.4",
"eslint": "^9.33.0",
"jest": "^30.0.5",
"typescript": "^5.9.2",
"webpack": "^5.101.1"
}
}
マルチステージビルドのDockerfileは先ほどの例のものを使用し、シングルステージビルドのDockerfileは以下を使用します。
FROM node
WORKDIR /usr/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
RUN npm install --production
EXPOSE 4000
CMD node dist/src/index.js
両者をビルドすると、以下の結果となりました。
シングルステージ 1.89GB
マルチステージ 1.14GB
マルチステージの方が0.75GB
小さいと確認できます。
これは、シングルステージのイメージには、ビルドに必要な開発用の依存関係やツールがすべて含まれていますが、マルチステージビルドでは、最終イメージからこれらが排除されているため、サイズが大幅に削減されているためです。
おわりに
このように、マルチステージビルドは、ビルド環境と実行環境を分離することで、最終的なDockerイメージを大幅に軽量化します。
ビルドに必要なツールや開発用の依存関係を最終イメージから除外することで、デプロイや管理がより効率的になります。
参考資料