Next.jsアプリケーションのDockerイメージのサイズが大きくて困っていたので調べていたところ、Next.jsの公式ドキュメントにDocker Imageというセクションがあり、おすすめ設定が記載されているのを見つけました。以前はここまで詳細な記述はなかったのですが、つい10日ほど前に追加されたようです。
ドキュメントには一番おすすめの方法だけ書かれているのですが、もともと「What is the best way to use NextJS with docker? · Discussion #16995 · vercel/next.js · GitHub」というdiscussionがあり、ドキュメントの記述はここでの議論が元になっているようです。Discussionではいくつか例が示されているのですが、それぞれさまざまな最適化テクニックが利用されており、どれくらいの容量になるか気になったので、Dockerfileを書いて調べてみました。
検証に使ったコードはこちらに置いてあります。
なお、各テクニックはNext.jsに限らずNode.jsのアプリケーション一般で通用するものだと思います。
まずはベースラインとして最適化なしのDockerfileです。
FROMnodeWORKDIR/usr/src/appCOPYpackage.json yarn.lock ./RUNyarn install --frozen-lockfileCOPY. .RUNyarn buildEXPOSE3000CMD["yarn","start"]
デフォルトのnode
イメージはDebianベースなので元からサイズが大きいです。イメージのサイズを小さくしたい場合は、可能であればまずはAlpine Linuxベースのnode:alpine
に乗り換えるのがいいでしょう。
(追記)Alpineでは標準Cライブラリとしてglibcではなくmusl-libcが使われており、Node.jsでもネイティブモジュールを使っている場合に互換性の問題が起きることがあります。2021年時点では、alpineではなく、glibcが採用されておりかつサイズが小さいdistrolessやslimイメージを使うのがおすすめです(ブックマークコメントでの指摘ありがとうございます)。
FROMnode:alpineWORKDIR/usr/src/appCOPYpackage.json yarn.lock /usr/src/appRUNyarn install --frozen-lockfileCOPY. .RUNyarn buildEXPOSE3000CMD["yarn","start"]
yarn install
はデフォルトではdevDependencies
も含めて全ての依存パッケージをインストールします。アプリケーション実行用のイメージにLinterやテストフレームワークなどの開発用ツールを含める必要はないので、--production
オプションを利用して、devDependencies
をインストールしないようにすると、イメージのサイズを小さくできます。
FROMnode:alpineWORKDIR/usr/src/appCOPYpackage.json yarn.lock ./RUNyarn install --production --frozen-lockfileCOPY. .RUNyarn buildEXPOSE3000CMD["yarn","start"]
さらに、Yarnのダウンロードキャッシュを削除すると、その分イメージのサイズを小さくできます。先程のdiscussionでは二つ方法が紹介されていました。
一つはyarn cache clean
コマンドを使う方法です。
FROMnode:alpineWORKDIR/usr/src/appCOPYpackage.json yarn.lock ./RUNyarn install --production --frozen-lockfile \ && yarn cache cleanCOPY. .RUNyarn buildEXPOSE3000CMD["yarn","start"]
もう一つは、キャッシュディレクトリを/dev/shm
以下にする方法です。/dev/shm
はLinuxにおけるRAMディスクのマウント先で、キャッシュディレクトリをここに指定すると、手で明示的にキャッシュを消さずとも、イメージのレイヤーにキャッシュが含まれなくなります。
ただし、この方法を利用する場合は、たいてい/dev/shm
のサイズが足りなくなる*1ため、docker build
コマンドの--shm-size
オプションで十分なサイズを指定してやる必要があります。一つめの方法を採用する方が面倒が少なくて楽でしょう。
FROMnode:alpineWORKDIR/usr/src/appCOPYpackage.json yarn.lock ./ENVYARN_CACHE_FOLDER=/dev/shm/yarn_cacheRUNyarn install --production --frozen-lockfileCOPY. .RUNyarn buildEXPOSE3000CMD["yarn","start"]
$ docker build--shm-size 1gb-f Dockerfile.dev-shm-yarn-cache
さらにイメージサイズを小さくするには、アプリケーションの実行に不要なコードをイメージに含めないようにするとよいです。テストコードは実行に関係ないので削除できますし、Next.jsのようにソースコードをトランスパイル・ビルドしている場合は、ビルド元のコードは不要です。
必要なファイルのみイメージに含める方法はいくつかありますが、ファイルの削除し忘れなどのミスを避けるには、multi-stage buildを使うとよいです。これはNext.jsのドキュメントでおすすめされている方法と同様です。この方法だと、Yarnのパッケージキャッシュを手で削除する必要がないのも楽です。
FROMnode:alpine AS depsWORKDIR/usr/src/appCOPYpackage.json yarn.lock ./RUNyarn install --production --frozen-lockfileFROMnode:alpine AS builderWORKDIR/usr/src/appCOPY. .COPY--from=deps /usr/src/app/node_modules ./node_modulesENVNODE_ENV=productionRUNyarn buildFROMnode:alpine AS runnerWORKDIR/usr/src/appCOPY--from=builder /usr/src/app/public ./publicCOPY--from=builder /usr/src/app/.next ./.nextCOPY--from=builder /usr/src/app/node_modules ./node_modulesEXPOSE3000CMD["yarn","start"]
では、それぞれのイメージのサイズを比較してみます。
$ docker images nextjs-docker--format"table {{.Tag}}\t{{.Size}}" |sed-e'1d' |sort-k2-h-r simple1.17GB# Debianベースalpine 399MB# Alpineベースinstall-production 315MB# yarn install --productiondev-shm-yarn-cache 157MB# キャッシュディレクトリを /dev/shm 以下にするclean-cache 157MB# yarn cache cleanstaged 156MB# Multi-stage build
結果、Next.jsのドキュメントでおすすめされている方法でビルドしたイメージが一番小さくなりました。全く最適化していないものに比べて1/8以下のサイズです。ベースイメージを変更したのが一番影響がありますが、他の手法もMB単位でサイズが小さくなっています。Multi-stage buildはこの比較だとあまり意味がないように見えますが、実際のアプリケーションでは実行時に不要なファイルがこのサンプルより多いでしょうから、ここで示したよりも効果が出るでしょう。
*1:デフォルトは64MB
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。