Next.js のプロジェクトを VPS 上の Docker にデプロイする【その2】
「Next.js のプロジェクトを VPS 上の Docker にデプロイする【その1】」の続きです。
前回は、本番環境用の Dockerfile の作成まで行いました。
今回は、本番環境でも Docker Compose 利用できるようにしていこうと思います。
本番環境用と開発環境用の違い
まずは、開発環境用に作成した docker-compose.yml と 公式サンプルの本番環境用の docker-compose.yml ファイルを見比べて、本番環境と開発環境のちがいを調査してみます。
開発環境用の docker-compose.yml
前回のシリーズの「Next.js のプロジェクトを作成して開発環境の Docker で動くようにする【その3】」で開発環境用に作成したdocker-compose.dev.yml
は以下の通りです。
version: '3'
services:
donuts-site:
container_name: donuts-site
build:
context: .
dockerfile: dev.Dockerfile
ports:
- 3000:3000
volumes:
- ./public:/app/public
- ./pages:/app/pages
- ./styles:/app/styles
公式サンプルの本番環境用の docker-compose.yml
Next.js 公式の docker-compose サンプルでの本番環境用のdocker-compose.dev.yml
をみてみます。
https://github.com/vercel/next.js/blob/canary/examples/with-docker-compose/docker-compose.prod.yml
version: '3'
services:
next-app:
container_name: next-app
build:
context: ./next-app
dockerfile: prod.Dockerfile
args:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
restart: always
ports:
- 3000:3000
networks:
- my_network
# Add more containers below (nginx, postgres, etc.)
# Define a network, which allows containers to communicate
# with each other, by using their container name as a hostname
networks:
my_network:
external: true
それぞれの違い
まず1つ目に networks
の定義です。
networks
の定義は、コンテナ同士が通信できるようにするための定義です。
現在外部のコンテナとの通信を行う予定がないので、今回は開発環境と同様に定義しないでおきます。(外部コンテナとの通信が必要なケースになったときにまた考えたいので)
2つ目はbuild
のargs
の有無です。
前回の記事で作成したdocker-compose.dev.yml
では定義していませんが、公式サンプルの開発環境のファイルではenvironment
でこれらの環境変数の設定を行なっています。
args
はbuildの配下に設定しますが、environment
はサービス名の配下に設定しています。
version: '3'
services:
next-app:
container_name: next-app
build:
context: ./next-app
dockerfile: prod.Dockerfile
# args は build 配下
args:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
# build の外に定義
environment:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
environment はドキュメントによるとコンテナ内での環境変数を指定することができます。
一方 args はドキュメントによると構築時に build のオプション(args)を追加することができます。
args は build用変数で、environment は環境変数ということになります。
開発環境と本番環境で同様の値を定義しているのでわかりづらいですが、args はdocker build
でイメージを作成するときに使われる変数で、environment はイメージからdocker run
でコンテナを起動するときに利用される変数になります。
参照: docker-compose コマンドでの args:, environment:, env_file: 及び .env ファイルの使い方
3つ目はvolumes
の有無です。
本番環境を更新する場合には、イメージを作成してビルドし直してデプロイします。
開発環境のように変更が即時に反映する必要もないので、利用しません。
最後にrestart
の有無です。
これは、コンテナが停止したときに自動でリスタートをするかどうかの設定になります。
本番環境ではサーバーが停止したなどの理由により、Docker 内のコンテナが停止してしまう可能性があります。そのときにサーバが復帰して Docker Engine も再起動した際に、自動でコンテナを再起動するかどうかの設定ができます。
再起動の条件についてはドキュメントを確認してください。
開発環境と本番環境の違いまとめ
開発環境
- 変更の即時反映が必要(volumes の利用)
本番環境
- 変更の即時反映が不要(volumes 不要)
- ビルド時に本番用のビルド変数の設定が必要(args の利用)
- コンテナの自動再起動(restart の利用)
本番用のdocker-compose.yml
開発環境と本番環境の違いを踏まえて、本番用にdocker-compose.prod.yml
を作成します。
# 作業ディレクトリに移動
$ cd ~/NextProjects/donuts-site/
# 本番用の docker-compose.yml を作成
$ touch docker-compose.prod.yml
version: '3'
services:
donuts-site:
container_name: donuts-site
build:
context: .
dockerfile: prod.Dockerfile
args:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
restart: always
ports:
- 3000:3000
dockerfile
には前回作った本番用の Dockerfile を指定しました。
docker compose で動作を確認
上記ファイルの動作確認を行います。
# 作業ディレクトリに移動
$ cd ~/NextProjects/donuts-site/
# ビルドを行う docker compose -f docker-compose.yml ファイルの指定 build
$ docker compose -f docker-compose.prod.yml build
# 起動する docker compose -f docker-compose.yml ファイルの指定 up -d(バックグラウンドで起動)
$ docker compose -f docker-compose.prod.yml up -d
http://localhost:3000 にてアクセスできることを確認します。
確認できたらコンテナとイメージの後始末をしておきます。
# コンテナを止める
$ docker compose -f docker-compose.prod.yml down
# コンテナが削除されていることを確認
$ docker container ls -a
# イメージは残っていることを確認
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
donuts-site_donuts-site latest 70d97c5956fd 2 minutes ago 121MB
# イメージも削除しておく
$ docker image rm donuts-site_donuts-site
無事に起動できましたが、docker compose up
の際に、以下の Warning が出ています。
WARN[0000] The "ENV_VARIABLE" variable is not set. Defaulting to a blank string.
WARN[0000] The "NEXT_PUBLIC_ENV_VARIABLE" variable is not set. Defaulting to a blank string.
これらのビルド変数は、.env
ファイルにて定義を行います。
公式サンプルの.envファイルをみてみます。
# DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults.
# If you want to add secrets use `.env.production.local` instead, which is automatically detected by docker-compose.
ENV_VARIABLE=production_server_only_variable
NEXT_PUBLIC_ENV_VARIABLE=production_public_variable
今更ですが、これらの環境変数はサンプルであり、現状では不要なものですね。
もし.env や .env.local 等の環境ファイルに変数を追加した場合には、docker-compose.yml の args にも忘れずに追加が必要という認識でよさそうです。
これらの話は今後必須にはなりますが、今回は一度保留にして先に進もうと思います。
最終的な本番環境のファイル
args はサンプルでの指定だったので削除しておきました。必要になった際に追記していきます。
version: '3'
services:
donuts-site:
container_name: donuts-site
build:
context: .
dockerfile: prod.Dockerfile
restart: always
ports:
- 3000:3000
Dockerfile は前回の記事(その1)で作成した時と変更点はありません。
##### deps ステージ #####
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
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-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
##### runner ステージ #####
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
これらの変更は GitHub のリポジトリにプッシュしておきます。
おわりに
本番環境用の Dockerfile と Docker Compose ファイルの用意ができました。
次回はこれらを利用して、実際に本番環境の Docker にデプロイを行っていこうと思います。
本番環境へのデプロイは、イメージの作成を外部で行ってから VPS に持っていくのか、VPS 上でイメージの作成から行うのか…前者が正解だと思っているのですが、もう少し調査が必要です。
前者の場合は「ConoHa で Docker 自動デプロイしたかったから、webhook サーバーを立てた。」の記事のように DockerHub を利用するかどうかも要検討です。
まだまだ先は長いですが頑張っていきます。