DevOctane
Xserver VPSDocker ComposeNext.jsVPSNginx

Xserver VPS に Docker Compose で Next.js をデプロイする手順【スタンドアロン出力】

2026.05.2635 min read
Xserver VPS に Docker Compose で Next.js をデプロイする手順【スタンドアロン出力】

VPSにNext.jsを本番デプロイするとき、多くの記事は next start をそのまま動かす構成を紹介している。しかしその方法では、node_modules を含む全ファイルをコンテナにコピーすることになり、Dockerイメージが1GB近くになることも珍しくない。

Next.jsには output: 'standalone' という設定があり、これを使うと本番実行に必要なファイルだけを含む軽量なビルド出力が得られる。.next/standalone/server.jsnode で直接実行するシンプルな構成で、イメージサイズを数百MB単位で削減できる。

この記事では、Xserver VPSにUbuntu 24.04でDocker Composeを使い、Next.jsをスタンドアロン出力で本番デプロイする手順を一から解説する。静的ファイルをNginxで直配信する構成・Xserver VPS特有のパケットフィルター設定・多くの記事が見落としている HOSTNAME 環境変数の設定まで含めて書く。

この記事でわかること
① Xserver VPSの特徴とConoHaとの違い(データ転送量・NVMe SSD・価格)
② Ubuntu 24.04でのサーバー初期設定(SSH・ufw・パケットフィルター)
③ Docker & Docker Compose のインストール手順(2026年版)
output: 'standalone' を使ったマルチステージDockerfileの正しい書き方
⑤ Nginxで /_next/static/ を直配信するDocker Compose構成

この記事の対象読者

  • Xserver VPSを契約済み、またはこれから契約する予定のエンジニア
  • Next.jsアプリを本番VPSで動かしたい
  • next start ではなくコンテナ化した構成で運用したい
  • 静的ファイルをNginxで直配信してパフォーマンスを上げたい

逆に、大量トラフィックやオートスケールが必要な用途はECS FargateやCloud Runのようなマネージドコンテナサービスの方が適している。VPS固定リソースのシンプルな運用向けの構成だ。


なぜ Xserver VPS を選ぶのか

Xserver VPSを選ぶ理由として最も大きいのは、データ転送量が無制限という点だ。

ConoHa VPSは共有100Mbps回線で「他のユーザーへの影響が出るレベルのトラフィック」が発生すると帯域が500kbps前後まで絞られる仕様がある(実体験はConoHa VPS Docker記事に書いた)。Next.jsアプリは画像・JS・CSSなどの静的アセットを配信するため、アクセスが増えてきたときの帯域制限は痛手になる。Xserver VPSはデータ転送量無制限を明示しており、突然の帯域制限を気にせず運用できる。

また、全プランにNVMe SSDのRAID0構成が採用されており、DockerのビルドやNext.jsのファイルI/O処理が速い。

Xserver VPSConoHa VPS
ストレージNVMe SSD(RAID0)SSD
データ転送量無制限制限あり(共有100Mbps)
CPUAMD EPYC™ 3世代非公開
2GB / 36ヶ月約¥990/月約¥1,210/月
4GB / 36ヶ月約¥1,980/月約¥2,420/月

価格もXserver VPSの方が安く、スペック面でも優位性がある。

VPS各社の詳しい比較はVPS比較記事にまとめているので、まだVPSを選定中の場合はそちらも参照してほしい。

Xserver VPS — NVMe SSD・データ転送量無制限・月額¥990〜(36ヶ月)

Xserver VPS の料金を確認する →

※本リンクはアフィリエイトリンクです

前提条件

この記事では以下の環境を前提に書く。

項目内容
OSUbuntu 24.04 LTS
Docker Engine26.x(2026年5月時点)
Docker Composev2.x(docker compose コマンド)
Node.js20 LTS(Dockerイメージ内)
フレームワークNext.js 14 / 15(App Router対応)
ローカル環境macOS または Linux

Docker Compose v1(docker-compose)は非推奨
docker-compose(ハイフンあり)コマンドはすでに非推奨・EOL。2026年時点ではdocker compose(スペース区切り)が標準だ。古い記事の手順をコピーするときは要注意。


STEP 1:VPS を契約・OS を選択する

プラン選択

Next.jsアプリ単体なら2GBプランから始めれば十分動く。画像処理や複数コンテナを同居させる場合は4GBプランが安心だ。Xserver VPSはコントロールパネルからオンラインでスケールアップできるため、まず2GBで様子を見て必要になったら変更するのが無駄がない。

OS は Ubuntu 24.04 LTS を選ぶ

Xserver VPSのOSテンプレートはUbuntu・AlmaLinuxなど複数から選べる。Dockerの公式サポートが最も手厚く、情報量も多いのはUbuntuだ。24.04 LTSを選べばセキュリティアップデートが2029年まで保証される。

アプリイメージは選ばない
Xserver VPSにはWordPressやLAMPがプリインストールされた「アプリイメージ」がある。Dockerで自前管理したい場合は素のUbuntuを選ぶこと。アプリイメージを選ぶと既存のApache等が起動しており、80/443ポートが競合する。


STEP 2:サーバーの初期設定をする

Xserver VPSでサーバーを作成すると、rootユーザーのパスワードログインが有効な状態で起動する。以下の順で初期設定を行う。

2-1. SSH公開鍵をコントロールパネルに登録する

ローカルマシンで鍵ペアを生成する。

ローカル:SSH鍵ペアの生成
ssh-keygen -t ed25519 -C "your_email@example.com"
# ~/.ssh/id_ed25519 と id_ed25519.pub が生成される
cat ~/.ssh/id_ed25519.pub
# 表示された公開鍵をコピーしてXserverコンパネに登録する

Xserver VPSのコントロールパネル → 「SSH Key」→「SSH Keyを登録」で公開鍵を登録しておくと、サーバー作成時に自動で設定される。

2-2. root でログインして一般ユーザーを作る

rootでSSH接続
ssh root@<サーバーのIPアドレ>

一般ユーザーを作成してsudo権限を付与する。

一般ユーザー作成
adduser deploy
usermod -aG sudo deploy
 
# rootの公開鍵を新しいユーザーにコピー
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

2-3. SSH設定を強化する

sshd_config の変更
vim /etc/ssh/sshd_config
変更箇所
# rootログインを禁止
PermitRootLogin no
 
# パスワード認証を無効化(公開鍵のみ許可)
PasswordAuthentication no
 
# ポートをデフォルトから変更(任意だが推奨)
Port 2222
SSHサービス再起動
systemctl restart sshd

切断前に必ず別ターミナルで接続確認
sshd_configを変更した後、現在の接続を切る前に別のターミナルから新しい設定で接続できることを確認すること。設定ミスのまま切断するとVPSにアクセスできなくなる。

2-4. ufw でファイアウォールを設定する

ufw設定
# SSHポートを先に許可してから有効化する
sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

2-5. Xserver VPS のパケットフィルターを設定する

Xserver VPSにはコントロールパネル上に独自の「パケットフィルター」機能がある。ufwに加えてコントロールパネル側でもポートを開放しないと外部からアクセスできない。

コントロールパネル → サーバー詳細 → 「パケットフィルター」→「パケットフィルターの設定」から以下を許可する。

対象プロトコルポート
SSHTCP2222(変更した場合)
HTTPTCP80
HTTPSTCP443

ufwとパケットフィルターの両方が必要
「ufwで開けたのに接続できない」というトラブルの多くは、コントロールパネルのパケットフィルターが閉じていることが原因だ。うまく接続できない場合は必ず両方を確認してほしい。


STEP 3:Docker & Docker Compose をインストールする

3-1. 旧バージョンを削除する

旧バージョンのアンインストール
sudo apt-get remove docker docker-engine docker.io containerd runc

3-2. Docker Engine をインストールする

Dockerインストール(公式リポジトリから)
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
 
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
 
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
 
sudo apt-get update
sudo apt-get install -y \
  docker-ce \
  docker-ce-cli \
  containerd.io \
  docker-buildx-plugin \
  docker-compose-plugin

3-3. ユーザーを docker グループに追加する

dockerグループへの追加
sudo usermod -aG docker $USER
newgrp docker

3-4. 動作確認

Dockerの動作確認
docker --version
# Docker version 26.x.x
 
docker compose version
# Docker Compose version v2.x.x
 
docker run hello-world

Hello from Docker! と表示されれば成功だ。


STEP 4:Next.js をスタンドアロン出力に設定する

next.config.js(または next.config.ts)に output: 'standalone' を追加する。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
}
 
module.exports = nextConfig

この設定でビルドすると、.next/standalone/ に本番実行に必要なファイルだけが出力される。

ビルド後のディレクトリ構成
.next/
├── standalone/
│   ├── server.js          ← node で直接実行するエントリーポイント
│   ├── node_modules/      ← 本番に必要な依存のみ(ツリーシェイク済み)
│   └── .next/
│       └── server/        ← サーバーサイドのコード
├── static/                ← クライアント向け静的ファイル(standalone に含まれない)
└── ...
public/                    ← 画像・フォントなど

重要なのは、static/ ディレクトリは standalone/ の中に含まれないという点だ。Dockerfileで別途コピーする必要がある。これを知らないと静的ファイルがコンテナに入らず、ページのスタイルが崩れる原因になる。


STEP 5:Dockerfile を作る(マルチステージビルド)

マルチステージビルドを使い、本番イメージに含まれるファイルを最小限にする。

Dockerfile
# ---- Stage 1: 依存インストール ----
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
 
# ---- Stage 2: ビルド ----
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
 
# ---- Stage 3: 本番ランナー ----
FROM node:20-alpine AS runner
WORKDIR /app
 
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
# HOSTNAME=0.0.0.0 を明示的に設定する(後述)
ENV HOSTNAME=0.0.0.0
 
# スタンドアロン出力をコピー
COPY --from=builder /app/.next/standalone ./
# 静的ファイルを正しい位置にコピー
COPY --from=builder /app/.next/static ./.next/static
# public/ をコピー(OGP画像・フォントなど)
COPY --from=builder /app/public ./public
 
EXPOSE 3000
CMD ["node", "server.js"]

HOSTNAME=0.0.0.0 は必ず設定する

HOSTNAME 未設定でNginxからのリクエストが届かなくなる
Next.jsスタンドアロン出力の server.js は、HOSTNAME 環境変数が設定されていない場合に localhost(127.0.0.1)でバインドすることがある。この状態だとコンテナ内では起動しているのにNginxからのリクエストが届かず、502 Bad Gatewayになる。ENV HOSTNAME=0.0.0.0 をDockerfileに書いておけば確実に防げる。

pnpm を使っている場合

pnpm を使う場合の deps ステージ
FROM node:20-alpine AS deps
RUN npm install -g pnpm
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

.dockerignore を設定する

.dockerignore
node_modules
.next
.git
.env*
*.md

.next/ を除外しているのは、ローカルのビルドキャッシュがコンテナ内のビルドに干渉しないようにするためだ。docker compose up --build のたびにコンテナ内でフルビルドを実行する。


STEP 6:Docker Compose 構成を作る

プロジェクト構成
myapp/
├── docker-compose.yml
├── Dockerfile
├── .dockerignore
├── nginx/
│   └── default.conf
└── (Next.jsのソースコード)
docker-compose.yml
services:
  nextjs:
    build: .
    container_name: nextjs
    restart: always
    expose:
      - "3000"
    volumes:
      - nextjs_static:/app/.next/static
    environment:
      NODE_ENV: production
 
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - nextjs_static:/var/www/html/_next/static:ro
    depends_on:
      - nextjs
 
volumes:
  nextjs_static:

nextjs_static という名前付きボリュームを使い、Next.jsコンテナが持つ .next/static/ をNginxが読めるようにしている。

名前付きボリュームの初期化タイミング
Dockerの名前付きボリュームは、空の状態で初めて作成されるとき、コンテナのディレクトリ内容でInitializeされる。再デプロイ時に古い静的ファイルが残り続ける問題があるため、コードを更新して再デプロイするときは docker compose down -v でボリュームを削除してから起動し直す必要がある(後述)。


STEP 7:Nginx の設定をする

nginx/default.conf
server {
    listen 80;
    server_name _;
 
    # /_next/static/ はNginxが直接配信(1年キャッシュ)
    location /_next/static/ {
        alias /var/www/html/_next/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
 
    # それ以外はNext.jsコンテナへプロキシ
    location / {
        proxy_pass http://nextjs:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

/_next/static/ にはNext.jsがビルド時にコンテンツハッシュ付きのファイル名を付ける(例:_next/static/chunks/main-a1b2c3.js)。ファイルが更新されると必ずファイル名も変わるため、1年間キャッシュしても古いファイルを掴み続ける問題は起きない。

起動・動作確認

コンテナ起動
cd ~/myapp
docker compose up -d --build
 
# ログ確認
docker compose logs -f
 
# 動作確認(IPアドレスで確認)
curl http://localhost/

VPSのIPアドレスにブラウザでアクセスしてNext.jsアプリが表示されれば成功だ。


STEP 8:HTTPS(Let's Encrypt)を設定する

本番運用にはHTTPS化が必須だ。ドメインを取得してVPSのIPアドレスに向けておく必要がある。

docker-compose.yml(HTTPS対応版)
services:
  nextjs:
    build: .
    container_name: nextjs
    restart: always
    expose:
      - "3000"
    volumes:
      - nextjs_static:/app/.next/static
    environment:
      NODE_ENV: production
 
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - nextjs_static:/var/www/html/_next/static:ro
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    depends_on:
      - nextjs
 
  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
 
volumes:
  nextjs_static:
nginx/default.conf(HTTPS対応版)
server {
    listen 80;
    server_name example.com www.example.com;
 
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
 
    location / {
        return 301 https://$host$request_uri;
    }
}
 
server {
    listen 443 ssl;
    server_name example.com www.example.com;
 
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
 
    location /_next/static/ {
        alias /var/www/html/_next/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
 
    location / {
        proxy_pass http://nextjs:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}
証明書の初回取得
docker compose run --rm certbot certonly \
  --webroot \
  --webroot-path=/var/www/certbot \
  --email your@email.com \
  --agree-tos \
  --no-eff-email \
  -d example.com \
  -d www.example.com
 
# Nginxを再起動して証明書を読み込む
docker compose restart nginx
証明書の自動更新(cron)
# crontab -e で以下を追加
0 0 * * 0 cd ~/myapp && docker compose run --rm certbot renew && docker compose restart nginx

再デプロイの手順

コードを更新して再デプロイする際の手順をまとめておく。

再デプロイ(静的ファイル更新あり)
cd ~/myapp
 
# 最新コードをpull(GitHubで管理している場合)
git pull origin main
 
# ボリュームを削除してビルドし直す
docker compose down -v
docker compose up -d --build

-v でボリュームを削除しているのは、nextjs_static ボリュームに古いビルドの静的ファイルが残り続けるのを防ぐためだ。これをしないと古いJSファイルがNginxから配信され続け、画面が壊れることがある。

ダウンタイムを最小化したい場合
docker compose down -v の間は一時的にサービスが止まる。個人開発や副業プロジェクトであれば数秒のダウンタイムは許容範囲内だ。無停止デプロイが必要になったときは、Blue-Greenデプロイや docker compose up -d --scale を使う構成を検討すること。

GitHub Actionsで自動化する場合は以下のワークフローが基本形だ。

.github/workflows/deploy.yml
name: Deploy
 
on:
  push:
    branches: [main]
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: deploy
          key: ${{ secrets.VPS_SSH_KEY }}
          port: 2222
          script: |
            cd ~/myapp
            git pull origin main
            docker compose down -v
            docker compose up -d --build

VPS_HOSTVPS_SSH_KEY はGitHubリポジトリのSecretに登録しておく。


トラブルシューティング

502 Bad Gateway になる

Nginxが起動しているのにNext.jsコンテナに繋がらない場合。

コンテナの状態確認
docker compose ps
docker compose logs nextjs

Next.jsコンテナが起動していても502になる場合は HOSTNAME 環境変数を確認する。localhost バインドになっているとコンテナ外からアクセスできない。Dockerfileに ENV HOSTNAME=0.0.0.0 が設定されているかを確認する。

静的ファイルが古いまま(デプロイ後も更新されない)

ボリュームのリセット
docker compose down -v
docker compose up -d --build

VPSのIPにアクセスできない

ポート確認
docker compose ps
sudo ss -tlnp | grep :80

ufwだけでなくXserver VPSコントロールパネルのパケットフィルターも確認すること。両方でポートが開放されていないと外部からアクセスできない。

docker: command not found

dockerグループの反映
newgrp docker
# またはいったんログアウトして再ログイン

コンテナが起動しない・再起動を繰り返す

詳細ログ確認
docker compose logs --tail=100 nextjs

よくある原因:

  • next.config.jsoutput: 'standalone' が設定されていない → ビルドは成功するが server.js が出力されない
  • .next/static/ のCOPY命令が抜けている → スタイルが当たらないかビルドエラー
  • 環境変数不足(必要な .env の値がコンテナに渡っていない)

Dockerfileのビルドが遅い

マルチステージビルドとBuildKitのキャッシュを活用すると2回目以降のビルドが速くなる。

BuildKitを明示的に有効化
DOCKER_BUILDKIT=1 docker compose up -d --build

よくある質問(FAQ)

Q. App Router と Pages Router のどちらでも使える?

どちらでも使える。output: 'standalone' は Next.js のルーターに関係なく機能する。App RouterのServer ActionsやServer Componentsも含めてスタンドアロン出力に含まれる。

Q. APIルートはスタンドアロン出力に含まれるか?

含まれる。/api/*(Pages Router)や Route Handlers(App Router)はすべて server.js に含まれる。外部APIへのプロキシや認証ロジックもそのまま動く。

Q. public/ フォルダのファイルはNginxで配信しなくていいのか?

このDocker Compose構成では public/ の配信はNext.jsコンテナ(server.js)が担当する。ファイル数・サイズが少なければ問題ない。OGP画像など大量の静的アセットがある場合は public/ も別ボリュームでNginxに共有する構成に拡張できる。

Q. Vercelではなくわざわざ VPS を使う利点は?

Vercelの無料プランはAPIルートのタイムアウト(10秒)やサーバーレス関数のコールドスタートがある。バックグラウンドジョブや長時間処理を伴うAPIを動かす場合、VPSの方が柔軟だ。月額固定コストで使い方に制限がなく、個人開発では管理しやすい。

Q. 環境変数はどうやって渡すか?

開発環境は .env.local を使うが、Dockerコンテナには渡さないのが基本だ。VPS上では docker-compose.ymlenvironment に直接書くか、.env ファイルを env_file で読み込む。

環境変数の渡し方
services:
  nextjs:
    build: .
    env_file:
      - .env.production
    environment:
      NODE_ENV: production

.env.production はGitにコミットせず .gitignore に追加しておく。

Q. Node.js のバージョンは 20 でなくてもよいか?

Next.js 14/15 は Node.js 18.17 以上が必要だ。LTS版を使うのが安全で、2026年時点では Node.js 20(Active LTS)または Node.js 22(Current)が推奨される。node:20-alpinenode:22-alpine に変えても動く。


まとめ:今日からできるアクション

Xserver VPSへのNext.jsスタンドアロン出力 + Docker Composeデプロイの手順をまとめた。

  1. VPS契約:2GBプランから始め、docker stats でメモリ監視しながらスケールアップを判断する
  2. 初期設定:ufw設定に加えて、Xserver VPSコントロールパネルのパケットフィルターも忘れずに開放する
  3. next.config.jsoutput: 'standalone' を設定してスタンドアロンビルドを有効化する
  4. Dockerfile:マルチステージビルド + ENV HOSTNAME=0.0.0.0 が重要。.next/static/COPY も忘れずに
  5. Docker Compose:名前付きボリュームで .next/static/ をNginxに共有し、キャッシュ効率を上げる
  6. 再デプロイdocker compose down -v && docker compose up -d --build でボリュームごとリセットする

データ転送量無制限・NVMe SSD・月額¥990〜というXserver VPSのスペックは、Next.jsアプリのホスティングに向いている。個人開発から副業プロジェクトまで、固定コストで安心して動かせる環境を作ってほしい。

Xserver VPS で Next.js を本番運用しよう

NVMe SSD・データ転送量無制限・36ヶ月で月額¥990〜。個人開発のNext.jsアプリをVPSで動かすなら、Xserver VPSは有力な選択肢だ。まず公式サイトでプランを確認してみよう。

Xserver VPS のプランを確認する →

※本リンクはアフィリエイトリンクです

DO
この記事を書いた人
DevOctane

バックエンド/インフラエンジニア歴8年。SES客先常駐から大手自社開発企業へ転職後、フリーランスとして独立。AWS・コンテナ・FinOps・バックエンド領域を中心に現場で培った知識を発信しています。