ホストに Node.js を入れず、Docker だけで Next.js(App Router)のプロジェクト作成から next dev 起動までやります。create-next-app@latest を使うので、執筆時点では Next.js 16 系が入ります(15 系でも手順はほぼ同じです)。
Docker Desktop か Docker Engine + Compose が動いていれば足ります。プロジェクトがまだない前提で書いています。
ひとつだけ順番に注意があります。空の作業フォルダを作って create-next-app を先に回し、そのあとで Dockerfile などを足すこと。Docker 用ファイルを先に置くと create-next-app . は contains files that could conflict で止まります。
参考: Next.js — Deploying with Docker · Docker Docs — Compose
目次
- なぜ Docker で開発するか
- 始める前に
- 全体の流れ
- プロジェクトをコンテナで作る
- 開発用 Dockerfile
- docker-compose.yml
- .dockerignore
- 環境変数
- 起動と確認
- うまくいかないとき
なぜ Docker で開発するか
Node のバージョンや OS がバラバラだと「自分の PC では動く」が出ます。node:22-alpine のようなイメージで揃えておけば、docker compose up で同じ環境を再現しやすくなります。ホストのグローバル npm ともぶつかりにくいです。
ここで作るのは 開発用だけ です。コンテナ内で next dev を動かし、ソースはボリュームマウントでホットリロードさせます。Next.js カテゴリの環境構築は ローカル開発に閉じ、本番イメージ・クラウドデプロイは扱いません。Next.js 16 系だと起動ログに Turbopack と出ることがありますが、気にしなくて大丈夫です。
始める前に
- Docker が起動している(Desktop でも Engine でも可)
- 作業フォルダは空(
DockerfileやREADME.mdも置かない) - フォルダ名は
_で始めない(npm の命名規則で弾かれる) - パスに日本語やスペースがない方が無難(例:
C:\projects\my-next-app) - ポート 3000 が空いている(塞がっていれば
3001:3000などに変える)
create-next-app が終わったら package.json と package-lock.json があることを確認してから Docker ファイルを足します。docker compose up の前に .env.example(Git 用テンプレ) と .env.local(手元・コンテナ用、空で可) を用意します。
全体の流れ
flowchart LR A["1. 空フォルダ"] --> B["2. create-next-app"] B --> C["3. Docker ファイル追加"] C --> D["4. .env.example / .env.local"] D --> E["5. docker compose up"]Docker は2回だけ出てきます。ここを混同するとハマりやすいです。
| 段階 | コマンド | Dockerfile | 目的 |
|---|---|---|---|
| プロジェクト生成 | docker run + node:22-alpine |
不要 | 空フォルダに Next.js を作る |
| 日常の開発 | docker compose build / up |
自分で書く | next dev をホットリロード付きで動かす |
docker run は一時コンテナでコマンドを1回実行するだけです。docker compose は後から足した Dockerfile と docker-compose.yml を読みます。ホストに Node.js も create-next-app も要りません。初回は node:22-alpine が Docker Hub から取られます。
完成後のディレクトリはこんな感じです。
my-next-app/ Dockerfile docker-compose.yml .dockerignore .env.example .env.local package.json src/ app/ page.tsx layout.tsxプロジェクトをコンテナで作る
空のフォルダで create-next-app を実行します。App Router・TypeScript・src/ 構成にしています。
npx --yes は npx 側の Ok to proceed? (y) を、create-next-app の --yes はテンプレート選択の対話をスキップします。両方付けておくと止まりにくいです。
PowerShell(Windows)
mkdir my-next-appcd my-next-appdocker run --rm -it -v ${PWD}:/app -w /app node:22-alpine sh -c "npx --yes create-next-app@latest . --typescript --eslint --app --src-dir --no-tailwind --use-npm --yes"Git Bash(Windows)
Git Bash だと -w /app が C:/Program Files/Git/app に化けることがあります。PowerShell 用をそのままコピーしないでください。
mkdir my-next-app && cd my-next-appMSYS_NO_PATHCONV=1 docker run --rm -it \ -v "$(pwd -W):/app" \ -w /app \ node:22-alpine \ sh -c "npx --yes create-next-app@latest . --typescript --eslint --app --src-dir --no-tailwind --use-npm --yes"bash(macOS / Linux)
mkdir my-next-app && cd my-next-appdocker run --rm -it -v "$(pwd):/app" -w /app node:22-alpine sh -c "npx --yes create-next-app@latest . --typescript --eslint --app --src-dir --no-tailwind --use-npm --yes"ホストに Node.js があるなら、空フォルダで次を打っても同じです。以降の Docker 手順は共通です。
npx --yes create-next-app@latest . --typescript --eslint --app --src-dir --no-tailwind --use-npm --yes主なフラグ: --app(App Router)、--src-dir(src/ 配下)、--no-tailwind、--use-npm(package-lock.json 生成)、--yes(対話スキップ)。
終わると package.json・src/app/page.tsx などがホスト側にできます。npm install はコンテナ内で走ります。
ls package.json, src/app/page.tsxこの時点でブラウザを開く必要はありません。次に Docker ファイルを足します。
開発用 Dockerfile
package.json と package-lock.json が揃ったら、ルートに Dockerfile を置きます。
# Dockerfile(開発用)FROM node:22-alpineWORKDIR /appCOPY package.json package-lock.json* ./RUN npm ciCOPY . .EXPOSE 3000CMD ["npm", "run", "dev", "--", "-H", "0.0.0.0", "-p", "3000"]COPY package.json を先にしておくと、依存が変わらない限りビルドキャッシュが効きます。
CMD の -H 0.0.0.0 は外さないでください。デフォルトの localhost だけだと、コンテナの外(ホストのブラウザ)から繋がりません。
pnpm / yarn を使う場合は COPY と install コマンドを合わせて変えてください。
docker-compose.yml
ソースをマウントして、保存のたびにホットリロードさせます。
# docker-compose.ymlservices: web: build: context: . dockerfile: Dockerfile ports: - "3000:3000" volumes: - .:/app - /app/node_modules - /app/.next environment: - NODE_ENV=development - WATCHPACK_POLLING=true env_file: - .env.local.:/app— ホストのソースをコンテナに同期/app/node_modules— ホストとコンテナでバイナリが食い違うのを防ぐ/app/.next— ビルドキャッシュの競合を減らすWATCHPACK_POLLING=true— Windows / macOS で変更が拾えないときの保険
env_file は .env.local があるときだけ読み込まれます。次のセクションで空ファイルを作ってから初回起動してください。
Windows の Docker Desktop だと、ポーリングがないと変更検知が遅く感じることがあります。
.dockerignore
node_modules.next.git.gitignoreREADME.mdDockerfiledocker-compose.yml.env*.localnpm-debug.log*.env.local をイメージに焼き込まないためです。値は実行時に env_file で渡します。
環境変数
| ファイル | Git | 役割 |
|---|---|---|
.env.example |
入れる | キー名だけのテンプレ。チーム共有用 |
.env.local |
入れない | 実際の値。手元とコンテナ(env_file)用 |
.env.example(キー名だけ)
# 共有用テンプレ(値は空またはダミー)NEXT_PUBLIC_SITE_URL=API_SECRET=PowerShell
@'# 共有用テンプレ(値は空またはダミー)NEXT_PUBLIC_SITE_URL=API_SECRET='@ | Set-Content -Path .env.example -Encoding utf8bash / Git Bash
cat > .env.example << 'EOF'# 共有用テンプレ(値は空またはダミー)NEXT_PUBLIC_SITE_URL=API_SECRET=EOF.env.local(手元の値・空でも可)
PowerShell
New-Item -Path .env.local -ItemType File -Forcebash / Git Bash
touch .env.local値を足すときの例:
NEXT_PUBLIC_SITE_URL=http://localhost:3000API_SECRET=dev-only-secretNEXT_PUBLIC_ だけがクライアントに入る点は、ホストで next dev するときと同じです。.env.local は Git に入れません。clone した人は .env.example をコピーして .env.local を作り、値を埋めます。
cp .env.example .env.localPowerShell:
Copy-Item .env.example .env.localコピー後、.env.local に開発用の値を書いてください。
起動と確認
プロジェクトルートで:
docker compose builddocker compose uphttp://localhost:3000 を開き、src/app/page.tsx を編集して保存。数秒で画面が変われば OK です。ログに Ready と GET / 200 が出ていれば問題ありません。
止めるときは Ctrl+C のあと docker compose down。コンテナとボリュームごと消すなら docker compose down -v(node_modules 入りの匿名ボリュームも消えるので、次回は再インストールが走ります)。
既に create-next-app 済みのプロジェクトなら、Docker ファイルを足すところから同じ構成にできます。
うまくいかないとき
| 症状 | 対処 |
|---|---|
Ok to proceed? (y) で止まる |
npx --yes を付ける |
create-next-app が対話で止まる |
末尾に --yes を付ける |
contains files that could conflict |
空フォルダで CNA を先に。Docker ファイルは後 |
name cannot start with an underscore |
フォルダ名を my-next-app などに変える |
Git Bash で .../Git/app |
MSYS_NO_PATHCONV=1 と -v "$(pwd -W):/app" |
docker compose build 失敗 |
先に create-next-app を完了させる |
env_file エラー |
.env.local を空で作る |
| ブラウザで繋がらない | CMD に -H 0.0.0.0 があるか確認 |
| 保存しても反映されない | WATCHPACK_POLLING=true |
node_modules がおかしい |
匿名ボリューム /app/node_modules を確認 |
| ポート使用中 | ports: "3001:3000" などに変更 |
Windows なら WSL2 バックエンドの Docker Desktop の方が、ボリューム I/O とファイル監視が安定しやすいです。