Node.jsのDockerイメージを使ってマルチステージビルドをやってみた
Node.jsのサーバーアプリケーションをDockerで動かす上で、マルチステージビルドをやってみたくなったので試してみました。
マルチステージビルド
Dockerのイメージ作成時に行うパッケージのインストールやコンテナ内でのビルドといったタスクを階層毎に行う事と認識しています。
結果的に1階層で行うときよりもDockerイメージのサイズを抑えて、実行環境でのパフォーマンスを向上させたり、最終的なイメージにランタイムなどの必要なツールのみ追加してセキュリティリスクを抑えることができる良さもあるようです。
やってみる
Dockerfile.singleとDockerfile.multiを作って、それぞれビルドしてみます。
src/app.tsという簡単なスクリプトを置き、dist/app.jsにビルドしたものを出力するようなnpm scriptsを定義しています。ディレクトリ構成としては以下のようになります。
├── Dockerfile.single
├── Dockerfile.multi
├── node_modules
├── package-lock.json
├── package.json
├── dist
│ └── app.js
└── src
└── app.ts
マルチステージビルドをしなかった場合
Dockerfile.singleは以下のような内容にしました。
FROM node:18-slim
WORKDIR /usr/src/app
COPY . .
RUN npm ci
RUN npm run build
EXPOSE 8080
CMD [ "node", "dist/src/app.js" ]
$ sudo docker build -t single -f Dockerfile.single
このようなイメージサイズになりました。
マルチステージビルドをした場合
Dockerfile.multiは以下のような内容にしました。
FROM node:18-slim as deps
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
FROM node:18-slim as builder
WORKDIR /usr/src/app
COPY . .
COPY /usr/src/app/node_modules ./node_modules
RUN npm run build
FROM node:18-slim as runner
USER node
WORKDIR /usr/src/app
COPY /usr/src/app/dist ./dist
COPY /usr/src/app/node_modules ./node_modules
EXPOSE 8080
CMD [ "node", "dist/src/app.js" ]
$ sudo docker build -t multi -f Dockerfile.multi
このようなイメージサイズになりました。
少し分かりづらいですが、マルチステージビルドの方が10MBほど小さくなりました。
今回はパッケージもほとんど入っていなかったためnode_modulesをまるっと移行しましたが、ランタイム時に必要なパッケージのみインストールで問題ない状況になるとだいぶ変わりそうな気がします。
感想
まだ調整できる箇所はあるかと思いますが、これだけでも少しサイズを落とせることが分かったのは良かったです。
また、Goなんかだとaplineみたいな小さいイメージでコンパイル済みのものを動かせるようなのでより最適化できそうな気がします。
こういった技術もタイミング見て少しずつやっていければと思います。