Docker のマルチステージビルドとは?
はじめに
Cloud Run でアプリを公開した時、 Dockerfile を作成した。
開発環境で見かける Dockerfile と異なり、 FROM 命令が多い印象を受けた。
なぜ FROM が多いのか?
FROM node:20-alpine as base
# 作業ディレクトリを設定
WORKDIR /app
# 依存関係のインストール
FROM base as deps
COPY package.json yarn.lock ./
RUN yarn install
# ビルド
FROM base as build
COPY /app/node_modules ./node_modules
COPY . .
RUN yarn build
# 本番環境
FROM base as production
ENV NODE_ENV=production
# 必要なファイルをコピー
COPY /app/build ./build
COPY /app/public ./public
COPY /app/node_modules ./node_modules
COPY package.json ./
# アプリケーションを起動
CMD ["yarn", "start"]
# ポートを公開
EXPOSE 3000
マルチステージビルド
1つの Dockerfile に FROM を複数記述する方式は、マルチステージビルドと呼ぶ。
以下のメリットがあるとのこと。
-
イメージサイズの最小化
- 最終的な本番用イメージには、アプリケーションの実行に必要な最小限のファイルのみが含まれる。ビルドツールや中間生成物は含まれない。
-
セキュリティの向上
- ソースコードやビルドツールが最終イメージに含まれないため、攻撃対象が減少する。
-
ビルドプロセスの最適化
- 依存関係のインストールやビルドプロセスが分離されているため、キャッシュを効率的に利用できる。
-
開発と本番環境の分離
- 開発用のツールと本番環境で必要なものを明確に分離できる。
Dockerfile の説明
今回作成した Dockerfile は4つのステージで構成されている。
- base: 基本イメージを設定
- deps: 依存関係をインストール
- build: アプリケーションをビルド
- production: 本番環境用の最終イメージを作成
初めに、基本イメージを設定し、作業ディレクトリを指定する。
FROM node:20-alpine as base
WORKDIR /app
次に、deps ステージを作成し、基本イメージを元に依存関係をインストールする。
FROM base as deps
COPY package.json yarn.lock ./
RUN yarn install
次に、 build ステージを作成し、基本イメージを元にアプリケーションをビルドする。deps ステージからnode_modulesをコピーし、ソースコードをコピーしてビルドを実行する。
FROM base as build
COPY /app/node_modules ./node_modules
COPY . .
RUN yarn build
最後に、本番用イメージを作成する。必要なファイルのみを前のステージからコピーし、アプリケーションを起動するコマンドを設定している。
このイメージが Cloud Run でデプロイされるイメージとなる。
FROM base as production
ENV NODE_ENV=production
COPY /app/build ./build
COPY /app/public ./public
COPY /app/node_modules ./node_modules
COPY package.json ./
CMD ["yarn", "start"]
EXPOSE 3000
終わりに
最初この Dockerfile を見た時、FROM を1つにして、全てを1つのコンテナイメージでビルドすると良いと思った。
しかし、そうするとイメージサイズが大きくなり、不要なツールや中間ファイルが本番環境に含まれてしまう可能性がある。
確かに Google Cloud の料金を鑑みると、コンテナイメージを保存する Artifact Registry はイメージサイズが大きいほど料金が発生するし、 CI/CD を担う Cloud Build もビルド時間が長くなると料金が発生する。そして、昔からコンテナのイメージサイズを小さくするよう言われた記憶もある。
クラウドでコンテナを扱う時の良い手法と理解した。
Discussion