🐢

Next.jsをDockerで開発するときのハマりポイントとその解決法について

に公開

これは何?

ローカルで動かしていたNext.jsの開発環境をDockerに移した。
いくつかハマったので、解決法を備忘録として残す。

ローカルで動かしていた環境

  • Next.js 15.2.3
  • Postgres 16
  • bun 1.1.34
  • Prisma 6.3.1

Dockerに移行した結果の環境構成

  • Next.js 15.3.1
  • Docker Compose 2.32.4
  • Postgres 16
  • bun 1.1.34
  • Prisma 6.3.1

Dockerで動かすためにNext.jsを15.3.1にアップグレードした。

Dockerfileの構築

ハマりポイント

next buildに失敗。tsconfig.jsonのパスエイリアスをdockerで解決できず、↓を参考にwebpackのconfigを設定するも、失敗。
https://github.com/vercel/next.js/issues/60079

解決法

Next.jsではTurbopackのbuildが15.3からサポートされていたため、↓を参考に2025/04時点の最新版の15.3.1にアップグレードしてnext build --turbopackで解決。Turbopackはtsconfig.jsonに定義されているpathsの設定を読み取り、自動的に解決することができるためである。
https://nextjs.org/docs/app/api-reference/turbopack#module-resolution

Turbopackは2025/04時点ではプロダクションビルドにまだ対応してないが、開発環境で使うなら問題なしと判断。

最終的なDockerfileは以下のとおり。

Dockerfile
FROM oven/bun:latest

# create user
ARG username=vscode
ARG useruid=1001
ARG usergid=${useruid}
RUN groupadd --gid ${usergid} ${username} \
&& useradd -s /bin/bash --uid ${useruid} --gid ${usergid} -m ${username} \
#
# and add sudoers for install library post docker operation
&& apt-get update \
&& apt-get install -y sudo \
&& echo ${username} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${username} \
&& chmod 0440 /etc/sudoers.d/${username} \
#
# for prisma
&& apt-get install -y openssl

USER ${username}
WORKDIR /app

# for bun
COPY --chown=${username}:${username} package.json ./
COPY --chown=${username}:${username} bun.lockb ./
RUN bun install

# copies rest of application code
COPY --chown=${username}:${username} . .

RUN bunx prisma generate

RUN bun deployable-test

useradddev containerを使う想定で行ったが、最終的に使わなかった。が、まったく無駄な設定というわけではないので残した。

最終行のRUN bun deployable-testpackage.jsonscriptsで指定したビルドコマンドにあたる。

package.json
{
  "private": true,
  "scripts": {
    "build": "prisma generate && next build",
    "dev": "next dev --turbopack",
    "start": "next start",
    "test": "vitest",
    "lint": "next lint",
    "deployable-test": "next build --turbopack"
  },
# ... 省略

実はnext dev --turbopackでパスエイリアスの解決自体は可能なため、開発環境で動かす分にはNext.jsではビルドコマンドの実行は不要だと後でわかった。とはいえ、Dockerのビルドの段階でエラーが出てくれたほうが不具合調査が捗るので残している。しかし時間がかかる処理なので、不要と判明したら削除する。

ちなみにDockerで動かすにあたりstandaloneモードにしてイメージサイズの縮小を図ったが、プロダクションビルドにDockerを使わないのであまり意味がないかもしれない。

next.config.mjs
import {withSentryConfig} from '@sentry/nextjs';
/** @type {import('next').NextConfig} */

const nextConfig = {
  output:'standalone',
}

docker-compose構築

ローカルでの開発と同じように、ローカルのソースを変更したら開発サーバをホットリロードさせる構成としたかった。以下に課題を示す。

ハマりポイント

ローカルのソースを変更してもホットリロードされない。

当初はdev containerを使い、ワークスペースディレクトリとバインドマウントさせてローカルソースと同期させることで解決させようとした。しかしうまく動かなかったのと、dev containerのメモリ消費量とパソコンのバッテリー消費量が以外と多く、断念した。

次に↓を参考にWATCHPACK_POLLING=trueを有効にしてnext devした。しかしこれもうまくいかなかった。そもそもコンテナ上にコピーしたソースコードを編集しないとホットリロードされないので、いちいちコンテナに入って開発するのは辛いと感じた。
https://github.com/vercel/next.js/issues/36774

解決法

↓を参考にDocker composewatchモードを利用することにした。このモードでホットリロードに対応しているフレームワークを動かすと、dockerでホットリロードを実現できる。
https://docs.docker.com/compose/how-tos/file-watch/

.dockerignoreに指定すれば監視の対象外にできるため、更新の多そうな以下のファイルをwatchの対象外にした。

.dockerignore
node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
.next
*.swp
/scripts

また、package.jsonが編集されたらイメージを再ビルドする設定とした。

最終的なファイルは以下の通り:

docker-compose.yml
volumes:
  pg-data:

services:
  postgres:
    container_name: postgres
    image: postgres:16.4-alpine3.20
    volumes:
      - pg-data:/var/lib/postgresql/data
      - type: bind
        source: "./dump"
        target: "/dump"
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DATABASE=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_ROOT_PASSWORD=root
    healthcheck:
      test: pg_isready -U postgres -d postgres
      interval: 3s
      timeout: 3s
      retries: 5

  app:
    build:
      context: '.'
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    command: >
      sh -c "bun dev"
    user: vscode
    develop:
      watch:
        - action: sync
          path: .
          target: /app
        - action: rebuild
          path: ./package.json
    depends_on:
      postgres:
        condition: service_healthy

target: /app/appは、Dockerfileで指定したWORKDIR/appと同じ。
.envファイルの指定はしていない(next devで自動的に読み込んでくれるため)。

以下のbun devは、package.jsonscriptsで指定したnext dev --turbopackが実行されるよう指定している。

command: >
  sh -c "bun dev"

この状態で↓のコマンドでコンテナを起動し、ローカルのソースコードを編集するとホットリロードされた。

docker compose up --watch

dev containerで開発する手もないわけではないが、バインドマウントの指定が意図通りにいかない可能性が高く、かつ消費メモリも多く、これまでVSCodeで利用していた拡張機能を一から入れなおさないといけないのが辛さを感じた。

まとめ

これまでローカルで開発してきた体験をなるべく崩さず、かつ、開発環境のスピードも落とさずに開発するにはどうしたらよいか考えた結果、上記の設定となった。

そもそも、なぜ開発環境をDocker化したかというと、↓の記事のようにCIを動かしやすくするためだ。e2eをDocker環境で構築しておけば、github actionsで特殊な設定を多用せずともCIを動かせると想定している。
https://zenn.dev/ishiyama/articles/c85138b42e3e1f

この記事が誰かの参考になればうれしいです。

Discussion