Vercel + Dockerデプロイ
MegaShopの開発が完了し, Charlieは本番環境にデプロイする必要があります。Vercelはシンプルなデプロイプロセスを提供しますが, カスタマイズの余地は限られています。一方, Dockerは完全な制御を提供しますが, 設定が複雑です。Bobは柔軟性と信頼性を両立するデプロイソリューションを必要としています - フロントエンドとAPIにはVercelを, データベースとRedisにはDockerを使用します。
1. 学ぶ内容
- Vercelデプロイ:ゼロ設定 + Nitroプリセット + 環境変数
- Dockerデプロイ:Dockerfile + マルチステージビルド + docker-compose
- Node.jsデプロイ:standaloneプリセット + PM2
- 静的ホスティング:Cloudflare Pages / Netlify / S3
- MegaShop本番デプロイの実践ガイド:Vercel + Docker Compose
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) マルチプラットフォームデプロイの決定木
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が含まれています。
📖まとめ
- Vercel:ゼロ設定デプロイ, 小中規模チームに最適, APIとフロントエンドの統合ソリューション
- Dockerのマルチステージビルドで最適化されたイメージを生成し, Docker Composeでサービス群全体をオーケストレーション
- PM2クラスタモードでマルチプロセス動作とゼロダウンタイムリロードを実現
- Nginxリバースプロキシ + SSL + 静的リソースキャッシュ
- MegaShop推奨:Docker Compose (Nuxt + PostgreSQL + Redis + Nginx)
📝練習問題
- 基本問題 (難易度:⭐):MegaShopをVercelにデプロイし, 環境変数を設定し, SSRページが正常に動作することを確認してください
- 応用問題 (難易度:⭐⭐):Dockerfileとdocker-compose.ymlを記述し, ローカルで完全な環境 (Nuxt + PostgreSQL + Redis)を起動してください
- チャレンジ (難易度:⭐⭐⭐):Docker Compose, Nginx, SSLを使用した完全な本番デプロイを実装し, ヘルスチェックと自動再起動を設定してください
---|



