【ITニュース解説】Production Next.js Dockerfile: Multi-Stage, Standalone and Non-Root
2026年10月27日に「Dev.to」が公開したITニュース「Production Next.js Dockerfile: Multi-Stage, Standalone and Non-Root」について初心者にもわかりやすく解説しています。
ITニュース概要
Next.jsアプリの本番用Dockerイメージを最適化する。マルチステージビルドと「standalone」出力設定で、開発ツールやソースコードを含まない軽量・高速なイメージを作成。非rootユーザーでの安全な実行、環境変数の適切な扱い、ヘルスチェックの追加といった実践的なDockerfile構成を学ぶ。
ITニュース解説
本稿では、Next.jsアプリケーションを本番環境でDockerを使ってデプロイする際、どのように最適化されたDockerfileを作成し、運用すべきかについて解説する。システムエンジニアを目指す初心者にとっても、ウェブアプリケーションのデプロイにおける重要な概念を理解する手助けとなるだろう。Dockerは、アプリケーションとその実行に必要なすべての要素(コード、ランタイム、システムツール、ライブラリなど)を一つの「コンテナ」と呼ばれる自己完結型のパッケージにまとめる技術である。これにより、開発環境と本番環境の間で一貫した動作を保証し、環境構築の手間を大幅に削減できるという大きなメリットがある。
本番環境でNext.jsアプリケーションをDockerで動かす場合、最も重要な目標の一つは、Dockerイメージのサイズをできるだけ小さくすることである。イメージサイズが小さければ、デプロイ時間が短縮され、サーバーのリソース消費も抑えられるため、運用コストの削減にもつながる。この目標を達成するための鍵となるのが、「マルチステージビルド」「output: 'standalone'設定」「非rootユーザーでの実行」の三点である。
まず「マルチステージビルド」について説明する。一般的なアプリケーション開発では、コードのビルド(コンパイルやトランスパイルなど)に多くのツールやライブラリが必要となる。例えば、Next.jsアプリケーションの場合、TypeScriptのコンパイラやESLint、npm/Yarnといったパッケージマネージャー、さらにビルド時に一時的に生成されるキャッシュファイルなどが含まれる。しかし、これらはアプリケーションが実際に動作する本番環境では不要なものである。マルチステージビルドとは、一つのDockerfile内で複数のビルドステージを定義し、最終的に本番環境で実行に必要なファイルだけを、最後のステージにコピーして新しいイメージを作成する手法である。これにより、ビルドに必要な巨大なツール類が最終イメージに含まれず、大幅なサイズ削減が可能となる。
具体的には、このDockerfileでは三つのステージを使用する。一つ目は「deps」ステージである。このステージでは、package.jsonとロックファイル(package-lock.jsonなど)だけをコピーし、依存関係のインストール(npm ci)を行う。ソースコードをまだコピーしないため、依存関係が変更されない限り、このステージのキャッシュが再利用され、ビルド時間を短縮できる。二つ目は「builder」ステージで、ここでdepsステージでインストールされたnode_modulesをコピーし、さらにアプリケーションのソースコードをコピーして、npm run buildを実行し、Next.jsアプリケーションをビルドする。そして三つ目の「runner」ステージが最終的な本番環境用イメージとなる。このステージは、クリーンなNode.jsイメージから開始し、builderステージで生成されたビルド成果物(standalone出力、静的ファイル、publicフォルダなど)だけをコピーする。これにより、開発・ビルドに必要な巨大なファイル群が一切含まれない、極めて軽量なイメージが完成する。
次に「output: 'standalone'設定」の重要性である。Next.js 16以降のバージョンでは、next.config.tsファイルにoutput: "standalone"という設定を追加できる。この設定を有効にすると、next buildコマンドの実行時に、.next/standaloneというフォルダが作成される。このフォルダには、アプリケーションの実行に必要な最小限のserver.jsファイルと、各ルートが実際に必要とするnode_modules内のファイルだけが厳選されてコピーされる。これにより、npm installなしでnode server.jsコマンドだけでアプリケーションを起動できるようになり、Dockerイメージのサイズを劇的に小さくできる。ただし、このstandalone出力では、public/フォルダや.next/staticフォルダ内のファイル(CSS、JavaScriptチャンク、画像など)は自動的にコピーされない点に注意が必要である。これらは通常CDNで配信されることを想定しているため、Dockerfileのrunnerステージで明示的にコピーする必要がある。これを怠ると、本番環境でアプリケーションのCSSや画像が表示されない、といった問題が発生する可能性がある。また、node server.jsでアプリケーションを起動することは、next startを使うよりも直接的で、Dockerのシグナル処理(docker stop時にSIGTERMが直接Node.jsプロセスに送られる)とも相性が良い。
さらに、Dockerコンテナを「非rootユーザーで実行する」ことも、セキュリティの観点から非常に重要である。デフォルトでは、Dockerコンテナはrootユーザーとして実行されることが多いが、これはセキュリティリスクを伴う。もしコンテナが不正アクセスされた場合、攻撃者がroot権限でホストシステムに影響を及ぼす可能性が高まるためである。公式のNode.js Dockerイメージには、あらかじめnodeという名前の非rootユーザー(UID 1000)が用意されているため、このユーザーを活用することが推奨される。Dockerfile内でUSER nodeという命令を追加することで、以降のコマンドがこの非rootユーザーとして実行されるようになる。また、アプリケーションがファイル書き込みを行う必要のあるディレクトリ(例: Next.jsがキャッシュを書き込む.nextフォルダ)に対しては、COPY --chown=node:nodeコマンドでファイルの所有権をnodeユーザーに変更したり、mkdir .next && chown node:node .nextのようにディレクトリを作成し、所有権を付与したりすることが必要である。これにより、権限不足による「EACCES」(アクセス拒否)エラーを防ぐことができる。
環境変数の扱い方も、Next.jsとDockerを使う上で注意すべき点である。Next.jsの環境変数には、ブラウザ側で利用されるNEXT_PUBLIC_で始まるものと、サーバー側だけで利用されるものがある。NEXT_PUBLIC_で始まる環境変数は、アプリケーションのビルド時にJavaScriptバンドルの中に埋め込まれるため、Dockerビルド時に--build-argオプションを使って渡す必要がある。一度ビルドされると、これらの値は固定され、後から変更するにはアプリケーションを再ビルドする必要がある。一方、データベースのURLやAPIキー、認証情報など、機密性の高いサーバー側の環境変数は、ビルド時には含めず、コンテナの実行時にdocker run -eオプションやデプロイプラットフォームの環境設定機能を使って渡すべきである。これにより、機密情報がDockerイメージの中に含まれてしまうことを防ぎ、セキュリティを確保できる。もしビルド時にどうしても機密情報が必要な場合は、BuildKitのシークレットマウント機能を利用することで、一時的にビルドステップ中にだけアクセスさせ、イメージの履歴に残さないようにできる。
アプリケーションの信頼性を高めるためには、「Health Check」の導入も不可欠である。Health Checkは、Dockerコンテナが正常に動作しているか(つまり、アプリケーションが応答しているか)を監視するための仕組みである。異常が検出された場合、DockerやKubernetesのようなコンテナオーケストレーションツールは、自動的にコンテナを再起動したり、トラフィックのルーティングを変更したりして、システムの安定性を保つ。Next.jsアプリケーションでは、/api/healthのような軽量なAPIルートを作成し、200 OKのステータスコードを返すように実装することが一般的である。DockerfileのHEALTHCHECK命令では、このヘルスチェックエンドポイントに対し、Node.jsの組み込みfetch機能(curlがインストールされていないスリムなイメージでも動作するため)を使ってリクエストを送り、応答を確認するように設定する。--start-periodでコンテナ起動直後の猶予期間を設けたり、--intervalでチェック間隔、--timeoutでタイムアウト時間、--retriesでリトライ回数を指定したりすることで、適切な監視設定を行うことができる。
画像最適化ライブラリのsharpに関する注意点も挙げておく。Next.js 15以降では、Next.jsがsharpを依存関係として自動的に組み込み、standaloneトレース機能がsharpとそのlibvipsバイナリ(Linux用)をイメージにコピーしてくれるため、通常は特別な設定なしで動作する。重要なのは、依存関係のインストールをDockerコンテナ内で行い、ローカル環境のnode_modulesをDockerイメージにコピーしないことである。これにより、正しいLinux用のバイナリがインストールされる。もしsharp関連の警告が表示される場合は、docker build --platform linux/amd64のようにビルドプラットフォームを明示的に指定したり、outputFileTracingIncludes設定で明示的にsharp関連ファイルをトレースに含めたりするなどの対処法が考えられる。
Dockerビルドの高速化も重要なテーマである。先述のマルチステージビルドによるキャッシュの活用に加え、Dockerfileの記述順序も重要となる。変更頻度が低いレイヤーをDockerfileの上部に配置し、変更頻度が高いレイヤーを下部に配置することで、Dockerのレイヤーキャッシュが効果的に利用され、ビルド時間が短縮される。具体的には、依存関係のインストール(npm ci)をソースコードのコピーよりも先に行うことで、ソースコードの変更時には依存関係の再インストールが不要となり、キャッシュが効きやすくなる。また、.dockerignoreファイルを使って、.gitフォルダやnode_modules(ローカル環境のもの)、.envファイルなど、ビルドコンテキストに不要なファイルを無視させることで、ビルドの効率を向上させることができる。BuildKitのキャッシュマウント(--mount=type=cache)も、npmのダウンロードキャッシュやNext.jsのビルドキャッシュをビルド間で再利用するのに役立つ。
ベースとなるNode.jsイメージの選択もパフォーマンスと互換性に影響する。公式のNode.js LTS(Long Term Support)バージョンを使用することが推奨され、node:24-slim(Debianベース、glibc)は、幅広いネイティブモジュールとの互換性があり、ほとんどのケースで安全な選択肢である。node:24-alpine(Alpine Linuxベース、musl)はさらにイメージサイズが小さいが、musl libcが一部のネイティブモジュールと互換性がない場合があるため注意が必要である。バージョンの固定化(ARG NODE_VERSION=24-slim)と定期的なリビルドによって、セキュリティパッチの適用と最新の安定版維持が可能となる。
まとめると、Next.jsアプリケーションをDockerで本番環境デプロイする際には、output: 'standalone'設定を有効にし、依存関係のインストール、アプリケーションのビルド、そして最小限のランタイムイメージ作成という三つのステージを持つマルチステージDockerfileを利用することが推奨される。これにより、イメージサイズを大幅に削減できる。さらに、セキュリティのために非rootユーザーで実行し、ファイル所有権を適切に設定することが重要である。環境変数は、NEXT_PUBLIC_付きのものはビルド時に、それ以外の機密情報は実行時に渡すように区別する。デプロイの信頼性を高めるために、軽量なAPIルートを設けたHealth Checkを導入するべきである。これらの実践により、高速で安全、そして運用しやすいNext.jsアプリケーションのDockerイメージを構築し、様々なDockerホスト環境に柔軟にデプロイすることが可能となる。