Next.js のプロジェクトを作成して開発環境の Docker で動くようにする【その2】

Next.js のプロジェクトを作成して開発環境の Docker で動くようにする【その1】の続きです。

今回は、通常のnpm run dev で http://localhost:3000 にサイトが表示できている状態のNext.js プロジェクトを、Docker 環境で起動することを目標とします。

前回は npx create-next-appでプロジェクトを作成するところまで行いました。(進捗ほぼゼロ😂)

Docker で動かすために

Next.js をDocker環境で動かすということは、Docker コンテナを作成して実行するということになります。

Next.jsプロジェクトを開発環境と本番環境のDockerで動かしてみたい【その1】で動かした公式サンプルは Docker Compose を利用したものでした。
後々 Docker Compose も使いますが、仕組みの理解のために今回は使わずに1つずつやっていこうと思います。

代わりに with-dockerの公式サンプルを参考にします。

公式サンプルのDockerfileを利用する

with-dockerの公式サンプルのIn existing projectsのセクションの通りに行います。

STEP.1
Dockerfile のコピー
# プロジェクトのディレクトリに移動
$ cd ~/NextProjects/donuts-site/

# Dockerfile の作成
$ touch Dockerfile

Dockerfile にサンプルのDockerfileの内容をそのまま追記します。

STEP.2
standalone 指定を追加
next.config.jsファイルにoutput: 'standalone'を追記します。

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  output: 'standalone',
}

module.exports = nextConfig
standalone指定について

Next.js の公式ドキュメントによると、output: 'standalone'を指定することで、スタンドアロンモードに切り替えることができます。

スタンドアロンモードについてはNext.jsのスタンドアロンモードでビルドしたイメージを Cloud Run へデプロイするの記事がとてもわかりやすいです。

STEP.3
イメージを作成する

STEP2 で作成した Dockerfile をもとにdonut-site-imageという名前のイメージを作ります。

$ cd ~/NextProjects/donuts-site/
# docker build -t 作成するするイメージ名 もとになるDockerfileの場所
$ docker build -t donut-site-image .

# image が作成できたかの確認
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
donut-site-image latest 077450288328 25 seconds ago 118MB
STEP.4
イメージからコンテナを作成する

STEP3 で作成したイメージからコンテナを作成して起動します。

# イメージからコンテナを作成して起動する
$ docker run donut-site-image

Listening on port 3000

この状態で http://localhost:3000 にアクセスをします。

無事にDocker 環境で起動することができました。

STEP.5
後始末をする
コンテナはイメージからいつでも作り直すことができますので、一度削除をしておきます。

control+cでコンテナを止めます。

# コンテナの存在を確認する (-a オプションで起動していないコンテナも表示する)
$ docker container ls -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be23f40b5fd3 donut-site-image "docker-entrypoint.s…" 9 minutes ago Exited (0) About a minute ago musing_austin

# コンテナを削除する
$ docker container rm musing_austin

# 再度確認をして、なくなっていれば OK
$ docker container ls -a

また、Dockerfile があれば STEP3 のイメージの作り直しもいつでも行うことができます。

イメージも一度削除しておきます。

# イメージの存在を確認する
$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
donut-site-image latest 077450288328 11 days ago 118MB

# イメージを削除する
$ docker image rm donut-site-image

Untagged: donut-site-image:latest
Deleted: sha256:0774502883282cf34a7705410b94f312cbd2e624552e036be3b86fe164fca6b3

# 再度確認をして、なくなっていれば OK
$ docker image ls

Dockerfile を読み解く

あまりに長くなりすぎたので、別記事に切り分けました。
Next.js 公式サンプルの Dockerfile を読み解いてみた」に書きました。

開発環境の Dockerfile を作る

公式サンプルは本番環境向けに作られているので、開発環境向けの Dockerfile を用意してみます。

docker-composeの公式サンプルの命名を参考にdev.Dockerfileというファイルを用意しました。

dev.Dockerfile
# deps ステージ
FROM node:16 AS deps

WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
  else echo "Lockfile not found." && exit 1; \
  fi

# builder ステージ
FROM node:16 AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build

# runner ステージ
FROM node:16 AS runner
WORKDIR /app

ENV NODE_ENV=development

COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

CMD ["node", "server.js"]

この Dockerfile で STEP.3から実行してみます。

$ cd ~/NextProjects/donuts-site/
# docker build -f Dockerfile のファイル名 -t 作成するするイメージ名 もとになるDockerfileの場所
$ docker build -f dev.Dockerfile -t donut-site-image .

# image が作成できたかの確認
$ docker image ls

REPOSITORY                         TAG       IMAGE ID       CREATED              SIZE
donut-site-image                   latest    a6fe40f5aa14   About a minute ago   916MB


# イメージからコンテナを作成して起動する
$ docker run --name donut-site donut-site-image

Listening on port 3000

http://localhost:3000 にアクセスして無事に見れました。

おわりに

今回は Docker 環境で Next.js プロジェクトを起動することができました。
次回は Docker Compose を使い、より便利にコンテナの起動ができるようにしていこうと思います。