2022.08.22
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
マルチステージビルドとは
マルチステージビルドは、一連の手順を経て Docker イメージを作成するプロセスです。
各ステージは、ビルドに必要なツールを読み込んだり、削除したりするという共通なタスクを実行するベースの Docker イメージを経由してそれぞれのステージのイメージが作成されます。
マルチステージビルドのメリット
従来の Docker 環境の作り方ですと、開発環境用に一つ Dockerfile を用意して、アプリケーションの構築に必要なものを入れる。 そこから本番環境用に、 アプリケーションそのものとそれを動かすために必要なもののみを含むスリム化した Dockerfile をもう 1 つ用意するのが一般的でした。
この場合、1 つのプロジェクトに対して 2 つの Dockerfile を保守していく必要がありました。これはプロジェクトが増えていくと保守する量も増えてあまりコストパフォーマンスが高いとは言えない。
マルチステージビルドは、開発環境、テスト環境、本番環境にかかわらず、1 つの Dockerfile で共通部分をベースにして、それぞれの環境のイメージを作成することが可能です。そうすることで可読性も保守性も上がります。
マルチステージビルドの書き方
今回 node アプリの開発と本番環境のビルドを例とします。
まずは共通部分をベースとして書きます。
FROM node:16-buster-slim AS base
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
gnupg2 \
procps \
tzdata \
unzip \
wget
COPY ./entrypoint.sh /usr/bin/entrypoint.sh
RUN chmod +x /usr/bin/entrypoint.sh
FROM で node イメージを docker hub から読み込み、それぞれのサーバーに応じて必要なパッケージをインストールします。
そしてからまず開発環境から
FROM base as development
ENV NODE_ENV=development
WORKDIR /home/app
COPY ./app/src ./src
COPY ./app/package*.json ./
RUN npm i
RUN npm run build
ENTRYPOINT [ "entrypoint.sh" ]
FROM base as development で名前を付けることで、このステージは development であると指定できます。
ソースと package.json をコピーして npm install することで node_module がしっかり生成されます。
ここで npm run build している理由としては後で本番環境で使うためです。本番環境では src/ 配下のソースは必要なく、ビルドで生成された dist/ 配下のみ必要なので、development の段階ですることによって本番環境で dist のみコピーすればいいことになります。
続いて、開発環境で npm install して生成された node_modules は開発環境のみに使われるパッケージも含まれています。なので本番環境用にさらにスリム化された node_modules を用意します。
FROM base AS pruned
WORKDIR /home/app
COPY ./app/package*.json ./
RUN npm i --production
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
RUN node-prune
npm install –production にすることで本番に必要なパッケージのみインストールして、また node-prune というツールを使って node_module の不必要なファイルを削除します。
これで本番環境のイメージも作るための準備も終わりました。
FROM base AS production
ENV NODE_ENV=production
WORKDIR /home/app
COPY --from=development /home/app/dist ./dist
COPY --from=pruned /home/app/package*.json ./
COPY --from=pruned /home/app/node_modules ./node_modules
ENTRYPOINT [ "entrypoint.sh" ]
–from=development で development ステージでビルドされた dist をコピーし、 –from=pruned で pruned ステージで用意された package.json と node_modules を持ってきます。そうすることで本番環境には必要最低限のものしか読み込まれません。
entrypoint の書き方は自由です。
if [ $NODE_ENV == "development" ]; then
npm run start:dev
else
npm run start:prod
fi
以上説明した Dockerfile をつなげて書くと
FROM node:16-buster-slim AS base
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
gnupg2 \
procps \
tzdata \
unzip \
wget
COPY ./entrypoint.sh /usr/bin/entrypoint.sh
RUN chmod +x /usr/bin/entrypoint.sh
FROM base as development
ENV NODE_ENV=development
WORKDIR /home/app
COPY ./app/src ./src
COPY ./app/package*.json ./
RUN npm i
RUN npm run build
ENTRYPOINT [ "entrypoint.sh" ]
FROM base AS pruned
WORKDIR /home/app
COPY ./app/package*.json ./
RUN npm i --production
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
RUN node-prune
FROM base AS production
ENV NODE_ENV=production
WORKDIR /home/app
COPY --from=development /home/app/dist ./dist
COPY --from=pruned /home/app/package*.json ./
COPY --from=pruned /home/app/node_modules ./node_modules
ENTRYPOINT [ "entrypoint.sh" ]