Next.js Docker 自前ホスティング完全ガイド:Vercel からの卒業

Vercel は素晴らしいプラットフォームです。プッシュするだけでデプロイでき、設定もほぼ不要、パフォーマンスも抜群です。しかし、プロジェクトが成長するにつれて、ある「壁」にぶつかることがあります。
それはコストと制限です。
私が運営していた中規模サイトでは、画像の帯域幅や Serverless Function の実行時間制限により、毎月 Vercel に $50〜$100 を支払っていました。さらに、特定のバックエンドサービスとの連携や、VPC 内での運用が必要になった際、Vercel の環境が逆に制約となる場面も出てきました。
そこで、月額 $5 の VPS(仮想専用サーバー)と Docker を使った自前ホスティングに切り替えたところ、コストは 1/10 になり、インフラの自由度も劇的に向上しました。
ただ、Next.js の自前ホスティングは一筋縄ではいきません。特に Docker イメージの最適化や、ストリーミングレンダリング(Edge Runtime)の対応にはコツがいります。
この記事では、Next.js アプリケーションを Docker で本番環境にデプロイするための「最適解」を共有します。
Vercel vs 自前ホスティング:どちらを選ぶべき?
盲目的に自前ホスティングへ移行する前に、メリット・デメリットを整理しましょう。
| 項目 | Vercel | Docker 自前ホスティング |
|---|---|---|
| コスト | 重量課金(高くなる可能性あり) | VPS固定費(安価・安定) |
| デプロイ | Git Push だけ | パイプライン構築が必要 |
| 機能 | Edge Config, Analytics 等が即座に使える | すべて自分で用意する必要あり |
| スケーリング | 自動 | 手動(K8s 等が必要) |
| 制御 | プラットフォームの制限内 | OS レベルで完全制御 |
自前ホスティングが向いているケース:
- 予算が限られており、固定費で運用したい
- 特定のリージョンにデータを置きたい
- イントラネットや VPN 内で動かしたい
- Docker エコシステム(Docker Compose 等)を活用したい
ステップ 1: Next.js の設定最適化
Docker イメージを小さくするために、Next.js の Standalone モード を有効にします。これにより、依存関係(node_modules)の中から「実際に使われているファイルだけ」を抽出してくれます。
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
// 以前は推奨されていましたが、現在は standalone が主流です
// target: 'serverless',
};
export default nextConfig;この一行を追加して npm run build を実行すると、.next/standalone というフォルダが生成されます。これがデプロイに必要な全てを含んだ「ミニマルな Next.js」です。通常、数百 MB ある node_modules が、必要なものだけに絞られるため劇的に軽くなります。
ステップ 2: マルチステージ Dockerfile の作成
ここが最重要ポイントです。ただ COPY . . するだけの Dockerfile は作らないでください。イメージサイズが 1GB を超えてしまいます。
以下のマルチステージビルド用 Dockerfile を使いましょう。これは Next.js 公式の推奨をベースに、さらに最適化したものです。
# 1. 依存関係のインストール(Deps ステージ)
FROM node:20-alpine AS deps
WORKDIR /app
# パッケージマネージャに応じたロックファイルをコピー
COPY package.json package-lock.json* ./
# 依存関係をインストール(ci は clean install の略で本番向け)
RUN npm ci
# 2. ビルド(Builder ステージ)
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 環境変数はビルド時に必要な場合のみここで宣言
# ENV NEXT_PUBLIC_API_URL=https://api.example.com
RUN npm run build
# 3. 実行(Runner ステージ)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# nextjs ユーザーを作成(セキュリティのため root で実行しない)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# 静的ファイルをコピー
# standalone モードでは public と .next/static は自動で含まれないため手動コピーが必要
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# standalone ビルド成果物をコピー
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
USER nextjs
EXPOSE 3000
ENV PORT 3000
# ホスト名設定(コンテナ外からアクセスできるようにする)
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]ポイント解説
- alpine イメージの使用: 軽量な Alpine Linux ベースの Node.js イメージを使用。
- マルチステージ: ビルド環境(devDependencies含む)と実行環境を分離。
- 静的ファイルのコピー:
standaloneモードはpublicや.next/staticを含まないので、手動でコピーする必要があります。これを忘れると画像が表示されなかったり、CSS が当たらない問題が起きます。 - 非 root ユーザー: セキュリティのため、専用ユーザー
nextjsで実行します。 - HOSTNAME 設定: デフォルトだと localhost (127.0.0.1) でしかリッスンしない場合があるため、
0.0.0.0を明示します。
ステップ 3: Docker Compose で起動
ローカルでのテストや、本番での簡易デプロイには docker-compose.yml が便利です。
version: '3'
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: nextjs-app
restart: always
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
- NEXT_PUBLIC_API_URL=https://api.myapp.com起動コマンド:
docker-compose up -d --buildこれで http://localhost:3000 でアプリが動きます。
ステップ 4: Nginx リバースプロキシとストリーミング対応
本番環境では、Docker コンテナの前に Nginx を置くのが一般的です(SSL 終端、キャッシュ、ロードバランシングのため)。
しかし、ここで一つ大きな罠があります。Nginx のデフォルト設定は Next.js のストリーミング(App Router の逐次ロード)を壊します。
Nginx がレスポンスをバッファリング(溜め込み)してしまうため、Next.js が「データを少しずつ送る」処理をしても、Nginx が「全部揃ってからブラウザに送る」挙動をしてしまうのです。
これを防ぐための nginx.conf 設定:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# 重要:ストリーミングのためにバッファリングを無効化
proxy_buffering off;
proxy_set_header X-Accel-Buffering no;
}
}proxy_buffering off; が魔法の言葉です。これにより、AI の回答生成のような逐次表示UIが正しく動作するようになります。
よくあるトラブルと解決策
1. 静的ファイル(画像・CSS)が 404 になる
原因: Dockerfile で public フォルダや .next/static フォルダをコピーし忘れている。
対策: Runner ステージの COPY コマンドを確認してください。standalone ビルドはこれらをバンドルしません。
2. 環境変数が undefined になる
Next.js には「ビルド時」に埋め込まれる変数(NEXT_PUBLIC_)と、「実行時」に読み込まれる変数があります。
Docker イメージをビルドする際、.env ファイルは通常含まれません。
対策:
NEXT_PUBLIC_変数:docker build --build-arg NEXT_PUBLIC_API_URL=...で渡すか、ビルド用.envを一時的にコピーする。- サーバーサイド変数:
docker run -e DATABASE_URL=...で起動時に渡す。
3. メモリ不足(OOM Kill)
ビルド中にプロセスが殺されることがあります。Next.js のビルドはメモリを食います。
対策: Docker デーモンのメモリ割り当てを増やすか、VPS のスワップ領域を作成してください。
# 2GBのスワップファイルを作成する例
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfileまとめ
自前ホスティングは、最初は設定の手間がかかりますが、一度構築してしまえば「自由」と「安さ」が手に入ります。
output: 'standalone'でビルドを軽量化する- マルチステージビルド でイメージサイズを削減する
- 静的ファイルの手動コピー を忘れない
- Nginx のバッファリングをオフ にしてストリーミングを守る
この4点を守れば、Next.js はどこでも快適に動きます。あなたのアプリが大成功してトラフィックが爆増したとしても、VPS のプランを一つ上げるだけで済み、クラウド破産することはないでしょう。
Next.js Docker 自前ホスティング構築フロー
standalone モードの設定から Dockerfile 作成、Nginx 設定までの完全ガイド
⏱️ Estimated time: 1 hr
- 1
Step1: Next.js 設定の変更
`next.config.mjs` に `output: 'standalone'` を追加します。これにより、実行に必要な最小限のファイルを生成するようになります。 - 2
Step2: Dockerfile の作成
マルチステージビルドを採用した Dockerfile を作成します。Builder ステージでビルドし、Runner ステージでは `standalone` フォルダと `public`、`.next/static` フォルダのみをコピーして軽量化します。 - 3
Step3: Docker イメージのビルド
コマンド `docker build -t nextjs-app .` を実行してイメージを作成します。`NEXT_PUBLIC_` 系の環境変数が必要な場合は `--build-arg` で渡します。 - 4
Step4: コンテナの起動
`docker run -p 3000:3000 nextjs-app` で起動します。本番環境では `docker-compose` の使用を推奨します。 - 5
Step5: Nginx リバースプロキシの設定
Nginx 設定ファイルで `proxy_buffering off;` を指定し、ストリーミングレンダリングが正しく機能するようにプロキシ設定を行います。
FAQ
自前ホスティングでどのくらいコスト削減できますか?
静的リソース(CSS/JS)が 404 エラーになるのはなぜ?
ストリーミングレンダリング(ChatGPTのような表示)が動かない
ビルド時に環境変数が必要です。どうすればいいですか?
Docker イメージのサイズを小さくするには?
4 min read · 公開日: 2025年12月20日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


コメント
GitHubアカウントでログインしてコメントできます