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 全体の時間も短縮されています。
さらに、本番コンテナに余計なパッケージが入らないことで、脆弱性スキャンの結果もクリーンになりました。
本番に devDependencies や tsc が残っている必要は本来なかったので、「なぜ最初からこうしなかったのか・・・」という感想でした。
一点、ビルドキャッシュの扱いには注意が必要です。
COPY . . をステージの早い段階で書くとキャッシュが効きにくくなるので、依存インストール後にソースをコピーする順番を意識すると、ビルドが速くなります。
👍 まとめ
multi-stage build は、Dockerfile を少し書き換えるだけで 本番イメージを大幅に軽量化できる、費用対効果がかなり高い改善 です。
本番環境のセキュリティを考えると、ビルドツールを本番イメージに含めないのは自然な考え方です。
まだ single-stage で運用しているプロジェクトがあれば、ぜひ一度試してみてほしいです。
イメージが小さくなると、デプロイも速くなり、開発体験もかなり良くなります。