Azure App Service で Next.js が起動しない原因と対処 ― 8080 ポートの HTTP ping/「Cannot find module ‘../server/require-hook’」エラーを完全解説
「Azure DevOps での CI/CD は成功しているのに、Azure App Service(Linux コンテナ)にデプロイした Next.js が起動せずコンテナが即時終了する」。ログには HTTP ping に 8080 ポートで応答がない、あるいは Cannot find module ‘../server/require-hook’ が出ている――。この組み合わせは、ポート設定の不一致、node_modules の欠落、そして 起動タイムアウトのどれか(または複合)が原因で発生するのが典型です。本記事では、再現しやすいミスの構造と、最短で復旧させるための具体手順、そして運用でのつまずきを未然に防ぐためのベストプラクティスを網羅的にまとめます。
症状(よく見るログ)
- デプロイ自体は成功しているが、コンテナが数秒~数十秒で落ちる(再起動を繰り返す)。
- コンテナログに
HTTP ping failed with status '...' on port 8080、Port 8080 didn't respondといった出力。 - アプリ側の起動ログに
Error: Cannot find module '../server/require-hook'(Next.js の内部モジュールが見つからない)。 - 負荷やスケールアウト時のみ sporadic に発生する(起動に時間がかかるときに限り失敗)。
結論(最短ルート):
WEBSITES_PORT を 3000 に統一し、Dockerfile を 3000 を EXPOSEして next start -p 3000 で明示起動。node_modules をイメージ内に固定(マルチステージビルドまたはoutput: "standalone")し、起動に時間がかかるなら WEBSITES_CONTAINER_START_TIME_LIMIT を延長。これでほとんどのケースは解決します。
なぜ起きるのか(技術的背景)
App Service(Linux)では、コンテナが起動した直後に ヘルスプローブが行われ、指定ポートへ HTTP ping が送られます。設定がないと既定で 8080 を叩くことがあり、アプリが 3000 で待ち受けていると「応答なし」と誤判定されます。また、Oryx(App Service の自動起動スクリプト生成機構)を使う構成や PORT 未設定の環境では、起動スクリプト側が 8080 を再指定する挙動が発生し、ポート不一致がさらに増幅します。
一方で Cannot find module '../server/require-hook' は Next.js のコアモジュールが解決できない際に出る代表エラーで、デプロイ済みイメージに node_modules が含まれていない、あるいは 依存関係の配置(pnpm の仮想ストア等)が実行環境で再現されていないことが主因です。CI で node_modules を tar.gz に固めて持ち込み、起動時に展開する手法は、展開失敗・パス相違・権限問題で破綻しやすいパターンです。
最後に、Next.js は初回起動で 内部の依存関係読み込み・キャッシュ生成が走るため、イメージの作り方次第で 60 秒以上かかることもあります。App Service は既定の待機時間を超えると「起動失敗」と扱い、コンテナを停止します。
主な原因と対処(要約表)
| 課題 | 解決策 | 補足 |
|---|---|---|
| ポート設定の不一致 | App Service のアプリ設定に WEBSITES_PORT=3000 を追加。 Dockerfile で EXPOSE 3000 を宣言。 next start -p 3000 で明示起動(CMD または ENTRYPOINT)。 Oryx や起動スクリプトを使う場合は PORT を事前に 3000 へ設定し、8080 への上書きを防止。 | 設定反映後は再起動 or 再デプロイが必要。 |
| node_modules の欠落 | CI で npm ci --omit=dev または pnpm install --prod を行い、node_modules をイメージ内に固定。 マルチステージ Docker ビルドで node_modules と .next をコピー。 Next 13+ は next.config.js に output: "standalone" を設定し、.next/standalone を実行する方式が安定。 | Next の next/react/react-dom は dependencies 側に置く。 |
| 初期化遅延によるタイムアウト | アプリ設定に WEBSITES_CONTAINER_START_TIME_LIMIT=1800(秒)を設定し、最大 30 分まで待機。 不要ファイルを省き、next build の成果物を最小化(standalone)。 ランタイムの CPU/メモリ不足を避ける(SKU 見直し)。 | スケールアウト時の遅延も短縮できる。 |
| トラブルシューティング不足 | 「コンテナの診断ログ」を有効化し、起動直後のログを確認。 ローカルで docker run -p 3000:3000 して再現検証。 コンテナ内で printenv、lsof -i -P -n、node -v を確認。 | ログ確認は解決の最短距離。 |
すぐに直す:最短 7 ステップ
- App Service のアプリ設定に
WEBSITES_PORT=3000を追加。 - 必要に応じて
WEBSITES_CONTAINER_START_TIME_LIMIT=1800を追加。 - Dockerfile を修正(
EXPOSE 3000、next start -p 3000)。 - CI で
npm ci --omit=dev(またはpnpm install --prod)を実行し、node_modules を固定。 - Next 13/14 の場合は
output: "standalone"を採用。 - ローカルで
docker run -e PORT=3000 -p 3000:3000→curl http://localhost:3000で動作確認。 - ACR へ push → App Service に再デプロイ → ログで 200 応答を確認。
実装例①:マルチステージ Dockerfile(npm)
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
# 本番に不要な devDependencies を除外
RUN npm ci --omit=dev
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
# Next のビルド
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
# (パターンA)standalone を使わない汎用構成
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node","node_modules/next/dist/bin/next","start","-p","3000"]
ポイント:
EXPOSE 3000とWEBSITES_PORT=3000を両方揃える。nextをdevDependenciesに置くと本番から消え、require-hookでコケやすい。dependencies側に移す。- CI で
npm prune --productionを雑に入れると Next まで削除されることがある。ロックファイルを信頼しnpm ci --omit=devを推奨。
実装例②:最小・高速な「standalone」ランタイム
Next 13+ では next.config.js に以下を指定できます。
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
};
module.exports = nextConfig;
Dockerfile は次のように単純化できます(推奨)。
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --omit=dev
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build # .next/standalone を生成
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
# standalone 実行に必要な最小ファイルのみコピー
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
# .next/standalone に含まれる server.js を起動
CMD ["node","server.js"]
standalone の利点:
- 本番に必要な依存関係だけが
.next/standalone配下に収まるため、イメージが小さく起動が速い。 node_modulesの配置や pnpm の仮想ストアに悩まされにくい。- 「require-hook が見つからない」系のエラーを実質的に回避しやすい。
実装例③:pnpm を使う場合の注意
# 例:pnpm
FROM node:20-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --prod --frozen-lockfile
FROM node:20-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node","server.js"]
注意点:
- pnpm の場合も
output: "standalone"を強く推奨。ワークスペース多段構成でのモジュール解決トラブルを避けやすい。 --frozen-lockfileを付けてロックファイルに厳密一致。
App Service のアプリ設定(環境変数)
WEBSITES_PORT=3000
WEBSITES_CONTAINER_START_TIME_LIMIT=1800
# 任意:
NODE_ENV=production
HOSTNAME=0.0.0.0
PORT=3000
# ログ永続化が必要なら:
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
補足:
- WEBSITES_PORT は必須級。Dockerfile の
EXPOSEと合わせて 3000 に統一。 PORTを 8080 にしてしまうと、next start -p 3000と不整合になり「応答なし」が再発。- ログがほしい場合は「App Service のログ」機能を有効化(外部ストレージや /home へ出力)。
ヘルスチェック(アプリ レベル)の追加
インフラのポート応答に加え、アプリ内に HTTP 200 を返すヘルスエンドポイントを持つと、再起動検知が安定します。
App Router(app/) の例:
// app/api/healthz/route.ts
export async function GET() {
return new Response('ok', { status: 200 });
}
Pages Router(pages/) の例:
// pages/api/healthz.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default function handler(_req: NextApiRequest, res: NextApiResponse) {
res.status(200).send('ok');
}
App Service の「健康チェック」のパスに /api/healthz を設定すれば、アプリ停止の早期検知や自動復旧に役立ちます。
Azure DevOps(ADO)パイプライン例
最小の雛形(ACR へ build/push → Web App for Containers へデプロイ)。
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
acrServiceConnection: 'svc-conn-acr' # ACR のサービスコネクション
azureSubscription: 'svc-conn-azure' # サブスクリプションのサービスコネクション
acrName: 'myacr.azurecr.io'
imageName: 'next-web'
tag: '$(Build.BuildId)'
steps:
* task: Docker@2
displayName: Build & Push
inputs:
containerRegistry: '$(acrServiceConnection)'
repository: '$(imageName)'
command: 'buildAndPush'
Dockerfile: '**/Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
latest
$(tag)
* task: AzureCLI@2
displayName: Set App Settings
inputs:
azureSubscription: '$(azureSubscription)'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az webapp config appsettings set
--resource-group
--name
--settings WEBSITES_PORT=3000 WEBSITES_CONTAINER_START_TIME_LIMIT=1800
* task: AzureWebAppContainer@1
displayName: Deploy to App Service
inputs:
azureSubscription: '$(azureSubscription)'
appName: ''
containers: '$(acrName)/$(imageName):$(tag)'
チェックポイント:
- ACR と App Service が同じリージョンにあると pull が高速。
containersで参照するregistry/image:tagが ACR 側の実体と一致していること。
起動トラブルの診断チェックリスト
- ポート一致:
WEBSITES_PORTとEXPOSE、next start -pがすべて 3000 で一致しているか。 - 依存関係:
node_modulesがイメージ内に存在するか(ls -la)。nextがdependenciesに入っているか。 - Next のビルド成果物:
.next(または.next/standalone)がコピーされているか。 - ランタイム:
node -vが Next のサポート範囲(LTS、例:18/20)か。 - 実ローカル検証:
docker run -e PORT=3000 -p 3000:3000…でcurlすると 200 が返るか。 - ログ:コンテナの起動直後ログにポートバインド、起動完了メッセージが出ているか。
- タイムアウト:初回リクエストまでに 60 秒以上かかっていないか。必要に応じ
WEBSITES_CONTAINER_START_TIME_LIMITを延長。 - ヘルスチェック:アプリ内の
/api/healthzに 200 が返るか。
「Cannot find module ‘../server/require-hook’」の深掘り
このエラーが出るときは、ほぼ次のどれかです。
- Next(npm パッケージ)が devDependencies 側にあり、本番ビルドで削除されている。
- CI で node_modules を tar.gz 化して持ち込むが、本番で
tar -xzfに失敗して展開されていない。 - pnpm の仮想ストアがランタイムに存在せず、シンボリックリンクが解決できない。
対処の要点:
next/react/react-domはdependencies側に移す。- standalone を使う(推奨)。
- どうしても tar.gz 方式を残すなら、起動前に
set -euo pipefailを付けて展開失敗を致命扱いにし、ログで必ず成功を検知。
起動時間短縮のテクニック
- イメージを小さく:
alpineベース、standalone、不要アセットの除外。 - キャッシュの活用:
package*.jsonの COPY を早め、npm ci層をキャッシュ。 - 初回実行時のコスト削減:画像最適化や SSR で重い処理を避け、必要なら CDN 側でキャッシュ。
- SKU の見直し:メモリ不足は GC を誘発し初回が遅くなる。
ローカルでの確実な再現手順
# 1) ビルド
docker build -t next-web:local .
# 2) 起動(PORT は 3000 に固定)
docker run --rm -e PORT=3000 -p 3000:3000 next-web:local
# 3) 確認(別ターミナル)
curl -I [http://localhost:3000/](http://localhost:3000/)
# HTTP/1.1 200 OK が返ること
コンテナに入って直接確認するのも有効です。
docker exec -it <container_id> sh
printenv | sort
lsof -i -P -n | grep LISTEN
node -v
ls -la node_modules/next/dist/server/ | head
運用でハマりがちな「落とし穴」と回避策
- 落とし穴:devDependencies の削除で Next まで消える
回避:npm ci --omit=devを使い、nextはdependencies側に移動。 - 落とし穴:PORT=8080 が紛れ込む
回避:CI と App Service の両方でWEBSITES_PORT=3000を宣言、Dockerfile のEXPOSE 3000と合わせる。 - 落とし穴:モノレポでコンテキストがズレる
回避:docker buildのコンテキストとWORKDIRをアプリ配下に固定、不要な親ディレクトリを含めない。 - 落とし穴:ログがどこにも残らない
回避:App Service のログ出力を有効化。stdout/stderrに出すだけでも十分役立つ。
セキュリティ・運用ベストプラクティス
- Node LTS を明示:
node:20-alpineなど、ベースイメージを固定。 - 非 root 実行:
USER nodeを runner ステージに追加(必要ファイルの権限に注意)。 - HEALTHCHECK の活用:Dockerfile に
HEALTHCHECK CMD wget -qO- http://localhost:3000/api/healthz || exit 1のような行を追加。 - Telemetry の無効化:ビルド時に
NEXT_TELEMETRY_DISABLED=1。 - 機密情報:環境変数やシークレットは App Service のアプリ設定で管理、イメージへ焼き込まない。
ケーススタディ:専用コンテナ方式へ切り替えて解決
相談案件では、これまで Oryx/既定設定に頼っていた構成から、専用コンテナ方式(マルチステージビルド+standalone)へ移行。以下を徹底した結果、デプロイ数分で安定稼働に至りました。
WEBSITES_PORT=3000を追加。- Dockerfile で
EXPOSE 3000とnext start -p 3000を明示。 next.config.jsにoutput: "standalone"。- CI で
npm ci --omit=devを適用し、node_modules と .next を確実にイメージへ。 - 初回時間がかかる想定で
WEBSITES_CONTAINER_START_TIME_LIMITを延長。
特に「Missing Node Modules」系のエラーは、standalone で最小依存のみを持ち込むことで一掃できました。スケールアウト時のウォームアップも短縮し、ピーク時の安定性が向上しています。
テキスト版フローチャート(原因切り分け)
コンテナが即時終了?
├─ YES: ログに "8080" で ping 失敗 → WEBSITES_PORT/EXPOSE/起動コマンドを 3000 へ統一
│ └─ 再発する → PORT を上書きしている env/スクリプトを再点検
├─ NO : 起動するが 502/504 → ヘルスチェックのパス設定・アプリ内 /api/healthz を確認
↓
"Cannot find module '../server/require-hook'"?
├─ YES: dependencies 側に next を移動、standalone で再ビルド、tar 展開方式を廃止
└─ NO : 起動に 60 秒+? → WEBSITES_CONTAINER_START_TIME_LIMIT を延長、イメージ縮小
↓
ローカル docker run -p 3000:3000 は成功?
├─ YES: App Service 側設定(ポート、ヘルスチェック、SKU)を再確認
└─ NO : Dockerfile/依存関係/Node バージョンに問題。ビルドログから差分特定
まとめ
- ポートは 3000 で統一(
WEBSITES_PORT・EXPOSE・next start -p)。 - node_modules はイメージ内に固定。Next 13/14 は standalone が最有力。
- 起動時間は延長+実装で短縮(
WEBSITES_CONTAINER_START_TIME_LIMIT、standalone、小さなイメージ)。 - ログとローカル再現が復旧の最短コース。
この 4 点を押さえれば、「HTTP ping 8080 応答なし」 と 「require-hook が見つからない」 の二大エラーは、再発しない堅牢な形に収束します。
付録:よく使うコマンド集
# アプリ設定(例・Azure CLI)
az webapp config appsettings set \
--resource-group <RG> \
--name <APP_NAME> \
--settings WEBSITES_PORT=3000 WEBSITES_CONTAINER_START_TIME_LIMIT=1800
# ログの追跡(例)
az webapp log tail -n -g --provider container
# ローカル検証
docker build -t next-web:local .
docker run --rm -e PORT=3000 -p 3000:3000 next-web:local
curl -I [http://localhost:3000/](http://localhost:3000/)
最終結果
依頼者は 専用コンテナ方式に切り替え、正しいポート公開と依存関係固定を行ったところ、数分で正常起動を確認。類似の “Missing Node Modules” エラーも、上記手順で node_modules を正しく含めれば解消可能です。
この記事が、App Service × Next.js の「起動しない」トラブルを再発させない設計・運用の指針になれば幸いです。

コメント