Next.jsをDockerで運用する方法 - Dockerfileの書き方を具体例で解説

Next.jsとDockerの関係性をざっくりと知ろう

Next.jsはReactをベースとしたフレームワークで、サーバーサイドレンダリングや静的サイト生成など、いろいろな機能を持っています。 そんなNext.jsをDocker上で動かすと、環境差異が減って扱いやすいと感じることが多いのではないでしょうか。

Dockerを使うメリットは、ローカル環境と本番環境で同じ設定を再現しやすい点にあります。 さらに、コンテナ同士を分離して管理できるので、依存関係の衝突を減らせるという利点もあります。 そのため、初めてNext.jsを使う皆さんにとっても、コンテナ化がシンプルに感じられることがあるかもしれません。

ただ、Dockerfileの書き方や設定内容を誤ると、ビルドに時間がかかったり、イメージサイズが大きくなったりすることがあります。 ここでは、初心者向けに実践しやすいDockerfileの作り方を解説していきます。

Dockerで得られる主なメリット

Docker化するとどんなメリットがあるのか、改めて具体的に挙げてみましょう。 皆さんがこれからNext.jsを学ぶ際にも、Dockerを使うかどうかの判断材料になりそうです。

  • 環境の一元管理がしやすい
  • ローカルと本番の設定をほぼ同じに保てる
  • 複数のサービスを連携しやすい
  • チーム開発での環境差異を小さくできる

例えば環境の一元管理ですが、Node.jsやOSのバージョンを合わせたり、パッケージをインストールしたりする作業がコンテナ内にまとまります。 この状態だと、皆さんのマシンに複雑なセットアップを行わなくても、コンテナを立ち上げるだけでNext.jsの開発に取りかかることが可能です。

また、ローカルと本番の設定を合わせるという面でも便利です。 Dockerfileで必要な手順を定義し、その手順通りにコンテナをビルドするため、設定の誤差が減るでしょう。

Dockerfileの基本構造を見てみよう

Dockerfileは、Dockerイメージをビルドするためのレシピのようなものです。 Node.jsをベースにして、Next.jsアプリを動かすイメージを作ることが多いですね。 ここでは大まかなDockerfileの流れを示しておきます。

# ベースイメージの指定
FROM node:18

# 作業ディレクトリを設定
WORKDIR /app

# 依存関係を先にコピーしてインストール
COPY package.json yarn.lock ./
RUN yarn install

# ソースコードをコピー
COPY . .

# ビルド
RUN yarn build

# ポートを指定
EXPOSE 3000

# 実行コマンド
CMD ["yarn", "start"]

まずFROM node:18の部分で、ベースとなる公式Node.jsイメージを指定しています。 Node.jsのバージョンはメンテナンスのされやすさを考慮すると、LTSの最新を選ぶとよいかもしれません。 次にWORKDIR /appで作業ディレクトリを設定し、そこにpackage.jsonyarn.lockをコピーしてからyarn installを実行します。

こうすることで、コード全体をコピーする前に依存パッケージをインストールできるため、イメージのビルドを効率化しやすくなります。 その後、ソースコードをすべてコピーしてyarn buildを実行し、本番用に最適化された出力を作成します。 最後にEXPOSE 3000と**CMD ["yarn", "start"]**でポートと起動コマンドを指定します。

Next.jsの開発をDocker化する具体的な流れ

Next.jsを最初に学び始めた皆さんがDockerを使うとき、どのように進めればいいでしょうか。 ここではシンプルな手順をお伝えします。

1. Next.jsプロジェクトを作成

ローカル環境でnpx create-next-app@latestなどを実行し、ひな形を作ります。

2. Dockerfileを用意

先ほど紹介したDockerfileをプロジェクトのルートディレクトリに作成します。

3. .dockerignoreを設定

node_modules.nextなど、ビルド不要のものを除外しておくと無駄に大きいイメージを作らずに済みます。

4. Docker build & Docker run

docker build -t my-next-app .でイメージを作り、docker run -p 3000:3000 my-next-appのように実行します。

この流れであれば、特別なスクリプトなしにコンテナ上でNext.jsアプリを動かせます。 もし複数のアプリケーションを連携させるなら、Docker Composeを使うとさらに管理が楽になります。

Docker Composeで複数のサービスを管理しよう

Next.jsを単体で動かすだけなら、先ほどのDocker buildとrunで十分です。 ただ、データベースなど複数のサービスを連携するときにはDocker Composeが便利ですね。

以下の例では、Next.jsとバックエンドAPI、そしてデータベースを同時に管理しているイメージを持っていただくためのサンプルを示します。 ここではあくまで流れを確認する目的なので、細かい設定は省略しています。

version: "3"
services:
  frontend:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - backend

  backend:
    image: node:18
    command: ["node", "server.js"]
    ports:
      - "4000:4000"
    depends_on:
      - db

  db:
    image: postgres:latest
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    ports:
      - "5432:5432"

frontendの部分でNext.jsを動かしている想定です。 Docker Composeを使えば、docker-compose up -dの一発で必要なコンテナがすべて起動します。 どこかのサービスにエラーがあっても、依存関係を見ながら立ち上げ順を制御してくれるので、全体管理が楽になります。

マルチステージビルドを使ってイメージを軽くする

Next.jsはビルド後に実行ファイルが生成されます。 このときにマルチステージビルドを活用すると、本番運用のコンテナをより軽くすることができるでしょう。

# ビルド用イメージ
FROM node:18 AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build

# ランタイム用イメージ
FROM node:18-alpine
WORKDIR /app

# ビルド成果物のみをコピー
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/package.json ./
COPY --from=builder /app/yarn.lock ./

RUN yarn install --production

EXPOSE 3000
CMD ["yarn", "start"]

前半がビルドステージ、後半がランタイムステージです。 ビルドステージではソースコードをコンパイルし、後半ではビルド成果物だけを小さいイメージへコピーしています。 こうすることで最終的なイメージから不要なファイルを省けるため、本番用のコンテナが少し軽くなるかもしれません。

マルチステージビルドはコンテナのサイズ管理がしやすい反面、設定を分ける手間が増える場合もあります。 まずは単一ステージでDockerを使い始めてから、余裕があればこの方法を検討するとスムーズに感じるでしょう。

本番環境でDockerを活用するときのポイント

実務でDockerを用いたNext.jsのデプロイを考えるなら、いくつか気をつけたい点があります。 単にイメージをビルドするだけでなく、再起動やスケーリングの仕組みまで設計することが多いからです。

例えば、コンテナオーケストレーションを行うために、KubernetesやDocker Swarmを導入するケースもあるでしょう。 この場合は、Next.jsのコンテナが落ちても自動的に再起動するように設定しておくと、サービスを止めにくくできます。

一方で、CDNの利用はNext.jsでよく検討されるトピックです。 Dockerと直接関係ないように見えますが、イメージをコンテナレジストリに配置したあと、CDN経由でデプロイを安定させるといった流れも考えられます。 本番ではアクセス負荷を想定して、レスポンスのキャッシュ戦略なども合わせて検討するとよいかもしれません。

コンテナイメージを複数環境にデプロイするとき、環境変数の管理にも注意が必要です。 あらかじめ.envファイルやCI/CDの設定で、各環境に合わせた値をセットできるように準備しておきましょう。

コンテナ上でのトラブルを想定しておこう

コンテナでNext.jsを動かしていると、いくつかのトラブルに遭遇するかもしれません。 代表的な例を挙げてみると、初心者の皆さんにも対処のイメージがつきやすいでしょう。

ポートの重複

ホスト側のポートを既に別のアプリで使っている場合、コンテナのポート割り当てに失敗することがあります。

ビルドエラー

Node.jsのバージョンや依存パッケージに互換性がないと、ビルド時にエラーが出るかもしれません。

メモリ不足

Dockerを動かすマシンのメモリが少ないと、ビルド中に突然落ちるケースもあります。

こういったトラブルを避けるには、Docker Composeで管理する際にコンテナ名やポートを明確に分けるとか、ビルドステージでキャッシュを有効活用するとかいった対策が考えられます。 また、次回以降のプロジェクトで同じエラーを繰り返さないように、Dockerfileをきちんと管理しておくと安心ですね。

追加の設定を入れてみるとどうなるか

Next.jsは環境変数を**.env**ファイルで管理できるため、Docker運用でもこの仕組みを活かすことがあります。 本番用の.env.productionや、ステージング用の.env.stagingなどを分けて作成するといいでしょう。

さらに、Next.jsではImage OptimizationやRoutingなど、多彩な機能を利用できます。 これらはDocker化しても基本的に使い方は変わりませんが、設定ファイルやビルドコマンドを少し変えるだけで動作に影響することもあります。 そのため、本番前にはテスト用のステージでコンテナを動かして挙動を確認してみるのがよさそうです。

マルチステージビルドだけでなく、Node.js本体のバージョンやAlpineベースのイメージを使うかどうかなど、細かい選択肢は複数あります。 プロジェクトの要件やチームの慣れに合わせて、最適なDockerfileを選ぶのも一つの考え方ですね。

まとめ

ここまで、Next.jsDockerで運用する際のメリットや具体的なDockerfileの書き方を見てきました。 単純にNext.jsのプロジェクトをコンテナ化するだけなら、最小限の設定で始められるでしょう。 一方で、本番運用やチーム開発を見据える場合は、マルチステージビルドやDocker Composeの活用、Kubernetesの検討なども大きなポイントになります。

Dockerを使うことで、環境構築の手間をかなり減らせる一方、Dockerfileや関連設定を正しく管理しないとビルドが遅くなったり、イメージが大きくなったりすることがあります。 ですので、まずは小さな範囲でDockerを活用しながら、少しずつ設定を最適化していくのが良いのではないでしょうか。

最終的に、皆さんが求める運用スタイルに合ったDockerfileを作り、Next.jsの特性を活かしやすい環境を構築できるようになると便利かもしれません。 ぜひ実際に手を動かして、コンテナ化の流れを体験してみてください。

Next.jsをマスターしよう

この記事で学んだNext.jsの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。