404 Not Found

404 Not Found


nginx

Vercel + Dockerデプロイ

MegaShopの開発が完了し, Charlieは本番環境にデプロイする必要があります。Vercelはシンプルなデプロイプロセスを提供しますが, カスタマイズの余地は限られています。一方, Dockerは完全な制御を提供しますが, 設定が複雑です。Bobは柔軟性と信頼性を両立するデプロイソリューションを必要としています - フロントエンドとAPIにはVercelを, データベースとRedisにはDockerを使用します。

1. 学ぶ内容


2. アーキテクトのリアルストーリー

(1) ペインポイント:ローカルで動くものが本番で動かない

CharlieのMegaShopはローカルで完璧に動作していましたが, サーバーにデプロイすると - 環境変数が不足し, データベースに接続できず, Redisが起動しておらず, ポートが競合しました。5回試してようやくデプロイに成功し, 毎回異なる問題に遭遇しました。

(2) Docker + Vercelソリューション

Dockerがランタイム環境全体をパッケージ化し, Vercelがフロントエンドをゼロ設定でデプロイします:

BASH
# Vercel:ゼロ設定デプロイ
vercel --prod

# Docker:一度ビルドすればどこでも実行可能
docker compose up -d

(3) 利点:ワンクリックデプロイ + 環境の一致性

Charlieはgit pushでVercelのフロントエンドデプロイをトリガーし, Docker Composeでデータベース, Redis, Nuxtサービスを管理して, 完全に一貫した環境を確保します。


3. Vercelへのデプロイ

(1) マルチプラットフォームデプロイの決定木

100%
flowchart TB
    A[Nuxt 3アプリをデプロイ] --> B{SSRが必要?}
    B -->|いいえ| C[静的ホスティング<br/>Cloudflare/Netlify/S3]
    B -->|はい| D{チーム規模?}
    D -->|小/中| E[Vercel<br/>ゼロ設定デプロイ]
    D -->|大/エンタープライズ| F[Docker + Node.js<br/>フルコントロール]
    E --> G[API + フロントエンドをVercelに]
    F --> H[Docker Compose<br/>Nuxt + PostgreSQL + Redis]

(1) ▶サンプル:Vercelのゼロ設定デプロイ

BASH
# Vercel CLIのインストール
npm i -g vercel

# デプロイ (Nuxt 3を自動検出)
vercel

# 本番デプロイ
vercel --prod

出力:

TEXT
# コマンド実行成功

(2) ▶サンプル:nuxt.config.ts Vercel設定

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'vercel' // Vercelが自動設定
  },
  // 環境変数はVercelダッシュボードで設定
  runtimeConfig: {
    databaseUrl: process.env.DATABASE_URL,
    redisUrl: process.env.REDIS_URL,
    public: {
      apiBase: process.env.API_BASE
    }
  }
})

出力:

TEXT
// 実行成功

(2) Vercelデプロイのメリットとデメリット

項目 Vercel Docker
設定の複雑さ 🟢 ゼロ設定 🔴 Dockerfileの記述が必要
デプロイ速度 ⚡ 30秒 🟡 3-5分ビルド
カスタマイズ 🔴 制限あり 🟢 フルコントロール
データベース 🔴 外部サービスが必要 (Vercel Postgres) 🟢 ローカルデプロイ
コスト ⚠️ リクエスト単位の課金 🟢 固定サーバー料金
スケーリング 🟢 自動 🟡 手動/K8s

4. Dockerデプロイ

(1) ▶サンプル:マルチステージDockerfile

DOCKERFILE
# Dockerfile

# ステージ1:依存関係のインストール
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# ステージ2:ビルド
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build

# ステージ3:本番
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# ビルド済みアセットのコピー
COPY --from=builder /app/.output ./.output
COPY --from=builder /app/prisma ./prisma

EXPOSE 3000

CMD ["node", ".output/server/index.mjs"]

出力:

TEXT
// 実行成功

(2) ▶サンプル:Docker Composeオーケストレーション

YAML
# docker-compose.yml
version: '3.8'

services:
  # Nuxtアプリケーション
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://megashop:megashop_pass@db:5432/megashop
      - REDIS_URL=redis://redis:6379
      - JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}
      - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  # PostgreSQLデータベース
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=megashop
      - POSTGRES_PASSWORD=megashop_pass
      - POSTGRES_DB=megashop
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U megashop"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # Redisキャッシュ
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # Nginxリバースプロキシ
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - web
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

出力:

TEXT
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

(3) ▶サンプル:Nginxリバースプロキシ設定

NGINX
# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream nuxt {
        server web:3000;
    }

    server {
        listen 80;
        server_name megashop.com;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl;
        server_name megashop.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;

        location / {
            proxy_pass http://nuxt;
            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;
        }

        # 静的アセットのキャッシュ
        location /_nuxt/ {
            proxy_pass http://nuxt;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

出力:

TEXT
// 実行成功

5. Node.jsスタンドアロン + PM2

(1) ▶サンプル:standaloneプリセットのビルド

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'node-server',
    compressPublicAssets: true
  }
})

出力:

TEXT
// 実行成功
BASH
# スタンドアロンサーバーをビルド
npm run build

# 出力:.output/server/index.mjs
# 直接実行
node .output/server/index.mjs

(2) ▶サンプル:PM2プロセス管理

JAVASCRIPT
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'megashop',
    script: '.output/server/index.mjs',
    instances: 'max',       // すべてのCPUコアを使用
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
      DATABASE_URL: 'postgresql://megashop:pass@localhost:5432/megashop',
      REDIS_URL: 'redis://localhost:6379'
    },
    max_memory_restart: '1G',
    error_file: './logs/error.log',
    out_file: './logs/out.log',
    merge_logs: true,
    autorestart: true
  }]
}

出力:

TEXT
// 実行成功
BASH
# PM2で起動
pm2 start ecosystem.config.js

# 監視
pm2 monit

# リロード (ゼロダウンタイム)
pm2 reload megashop

# ログ
pm2 logs megashop

(1) デプロイオプションの比較

ソリューション 複雑さ 管理性 スケーラビリティ 適用性
Vercel 🟢 低 🔴 低 🟢 自動 小/中規模チーム
Docker Compose 🟡 中 🟢 高 🟡 手動 中/大規模チーム
PM2 + Nginx 🟡 中 🟢 高 🟡 手動 VPSデプロイ
K8s 🔴 高 🟢 最高 🟢 自動 大規模

6. 総合例:MegaShop本番デプロイ

BASH
# ============================================
# MegaShop本番デプロイ
# Docker Compose + Nginx + PostgreSQL + Redis
# ============================================

# 1. クローンと設定
git clone https://github.com/megashop/megashop.git
cd megashop
cp .env.example .env
# 本番用シークレットで.envを編集

# 2. ビルドして起動
docker compose up -d --build

# 3. データベースマイグレーションの実行
docker compose exec web npx prisma migrate deploy

# 4. 初期データのシード (初回のみ)
docker compose exec web npx prisma db seed

# 5. 検証
curl https://megashop.com/api/products?limit=5

# 6. 監視
docker compose logs -f web

❓よくある質問

Q Vercelの無料枠で十分ですか?
A 無料枠には月間100GBの帯域幅と1,000時間のサーバーレス使用量が含まれます。MegaShopのISRキャッシュヒットは関数呼び出しとしてカウントされないため, 中程度のトラフィックなら無料枠で十分です。
Q Dockerマルチステージビルドの利点は何ですか?
A 最終イメージにはランタイムに必要なファイルのみが含まれ, ソースコードとdevDependenciesは除外されます。イメージサイズが1GB以上から約150MBに削減され, より安全で高速になります。
Q NuxtスタンドアロンとNodeプリセットの違いは何ですか?
A 違いはありません。node-serverプリセットはスタンドアロン出力 (.output/server/index.mjs)を生成し, node_modulesを必要とせず, 直接実行できます。
Q PM2クラスタモードはSSRをサポートしていますか?
A はい。各ワーカーは独立したプロセスで独自にSSRをレンダリングします。ISRキャッシュはRedisを通じて共有され, すべてのワーカーからアクセス可能です。
Q DockerでPrismaマイグレーションはどのように実行しますか?
A docker compose exec web npx prisma migrate deployを実行してください。またはDockerfileにマイグレーションステップを追加してください。本番ではmigrate deployを使用してください (新しいマイグレーションを作成せず, 既存のもののみを適用します)。
Q SSL証明書はどのように取得しますか?
A 本番環境ではLet's Encrypt + Certbotで自動更新を使用してください。Dockerデプロイではnginx-proxy + acme-companionで自動取得できます。VercelにはデフォルトでSSLが含まれています。

📖まとめ


📝練習問題

  1. 基本問題 (難易度:⭐):MegaShopをVercelにデプロイし, 環境変数を設定し, SSRページが正常に動作することを確認してください
  2. 応用問題 (難易度:⭐⭐):Dockerfileとdocker-compose.ymlを記述し, ローカルで完全な環境 (Nuxt + PostgreSQL + Redis)を起動してください
  3. チャレンジ (難易度:⭐⭐⭐):Docker Compose, Nginx, SSLを使用した完全な本番デプロイを実装し, ヘルスチェックと自動再起動を設定してください

---|

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%