はじめに

LINE BotとDify AIを連携させたWebhookサーバーを、Vercelからより柔軟性の高いGoogle Cloud Runに移行した際の知見を共有します。「VercelだけどDockerも使いたい」「コスト抑えつつスケーラブルに運用したい」という方の参考になれば幸いです。

この記事で得られること

  • Next.js 16アプリケーションの最適なDocker化手法
  • Google Cloud Runへのデプロイ方法(GUI/CLI両対応)
  • 本番環境での環境変数・シークレット管理のベストプラクティス
  • マルチステージビルドによるイメージサイズ最適化(60%削減)
  • 実際に遭遇したエラーと解決方法

技術スタック

  • フレームワーク: Next.js 16 (App Router + Turbopack)
  • ランタイム: Node.js 20 LTS
  • コンテナ: Docker (Alpine Linux)
  • デプロイ先: Google Cloud Run
  • シークレット管理: Google Secret Manager

なぜGoogle Cloud Runを選んだのか

Vercelとの比較

項目VercelCloud Run
デプロイの簡単さGit pushで自動CLI/GUI操作必要
柔軟性サーバーレス固定コンテナで自由
コスト無料枠あり、従量課金無料枠あり、使用分のみ課金
コールドスタート~100ms~300ms(最適化後)
カスタマイズ性制約あり完全制御可能
スケーリング自動(制御不可)細かく制御可能

Cloud Runを選んだ理由

  1. Dockerコンテナで動くものは何でもデプロイ可能 – 将来的な拡張性
  2. リクエストがない時は完全に0円 – 低トラフィック時のコスト最適化
  3. Secret Managerとの統合 – エンタープライズグレードのシークレット管理
  4. 細かいリソース制御 – メモリ、CPU、タイムアウト、並行処理数を調整可能

Docker化の全体設計

1. Next.js Standalone出力モードの採用

Next.js 16には「standalone」出力モードがあり、これを使うことでイメージサイズを劇的に削減できます。

next.config.mjs

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone', // この1行が重要
  typescript: {
    ignoreBuildErrors: true,
  },
  images: {
    unoptimized: true,
  },
}

export default nextConfig

効果:

  • 通常ビルド: 約500MB
  • standalone: 約150MB(60%削減

2. マルチステージビルドによる最適化

最終イメージに不要なものを含めないため、3段階のビルドプロセスを採用しました。

Dockerfile

# Stage 1: 本番依存関係のインストール
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Stage 2: アプリケーションビルド
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 3: 本番実行環境(最終イメージ)
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV PORT=3000

# 非rootユーザーを作成
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

# standalone出力をコピー
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

USER nextjs
EXPOSE 3000

CMD ["node", "server.js"]

ポイント:

  • depsステージ: 本番依存関係のみインストール(レイヤーキャッシュ活用)
  • builderステージ: 全依存関係でビルド実行
  • runnerステージ: 必要なファイルだけコピー、非rootユーザーで実行

最終イメージサイズ:

  • ディスク使用量: 292MB
  • 圧縮サイズ: 72MB(実際の転送サイズ)

3. .dockerignoreで不要なファイルを除外

ビルドコンテキストを最小化することで、ビルド速度が向上します。

.dockerignore

# 依存関係(コンテナ内で再インストール)
node_modules
npm-debug.log*

# Next.jsビルド出力(コンテナ内で再ビルド)
.next
out
build

# 環境変数(Secret Managerを使用)
.env*
!.env.example

# Git履歴(230KB以上削減)
.git
.gitignore

# IDE設定
.vscode
.idea

# ドキュメント
*.md
docs

# テスト
__tests__
*.test.ts
coverage

# 重要: tsconfig.jsonは除外しない(パスエイリアス解決に必要)

実際に遭遇したエラーと解決方法

エラー1: package-lock.jsonの同期エラー

npm error `npm ci` can only install packages when your package.json
and package-lock.json are in sync.
npm error Invalid: lock file's next@16.0.0 does not satisfy next@16.0.7

原因: package.jsonとpackage-lock.jsonのバージョン不一致

解決方法:

npm install

これでpackage-lock.jsonが同期されます。


エラー2: Next.jsのセキュリティ脆弱性

npm warn deprecated next@16.0.7: This version has a security vulnerability.
Please upgrade to a patched version.

原因: Next.js 16.0.7に重大な脆弱性

  • Server ActionsのソースコードExposure
  • DoS脆弱性

解決方法:

npm audit fix --force

Next.js 16.0.10にアップデート(セキュリティパッチ適用済み)


エラー3: モジュール解決エラー(最も時間を使った)

Module not found: Can't resolve '@/lib/dify/client'

原因: .dockerignoretsconfig.jsonを除外していたため、パスエイリアス @/* が解決できない

解決方法:

# .dockerignore

# Config files
.prettierrc
.eslintrc
- tsconfig.json
+ # tsconfig.json is needed for path aliases (@/*)

学び: tsconfig.jsonはNext.jsのビルドに必須。安易に除外リストに入れないこと。

Google Cloud Runへのデプロイ

方法1: GUI方式(初心者向け)

コマンドラインが苦手な方でも、ほぼGUIだけでデプロイできます。

ステップ1: Secret Managerでシークレット作成

  1. Secret Managerにアクセス
  2. 「シークレットを作成」で以下を登録:
シークレット名
line-access-token.env.localのLINE_CHANNEL_ACCESS_TOKEN
line-channel-secret.env.localのLINE_CHANNEL_SECRET
dify-api-key.env.localのDIFY_API_KEY

なぜSecret Manager?

  • 環境変数に直接書くと履歴に残る
  • Secret Managerは暗号化され、アクセス制御も可能
  • ローテーションも簡単

ステップ2: Artifact Registryでリポジトリ作成

  1. Artifact Registryにアクセス
  2. リポジトリ作成:
    • 名前: line-chatbot
    • 形式: Docker
    • リージョン: asia-northeast1(東京)

ステップ3: イメージビルド(ここだけCLI必須)

gcloud config set project YOUR_PROJECT_ID
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/line-chatbot/server:latest

Cloud Buildが自動的に:

  1. Dockerfileを検出
  2. マルチステージビルドを実行
  3. Artifact Registryにプッシュ

約3〜5分で完了します。


ステップ4: Cloud Runでサービス作成

  1. Cloud Runにアクセス
  2. 「サービスを作成」
  3. 主要設定:

基本設定:

  • イメージ: Artifact Registryから選択
  • サービス名: line-chatbot-server
  • リージョン: asia-northeast1(東京)
  • 認証: 未認証の呼び出しを許可(LINEからのWebhook受信用)

コンテナ設定:

  • ポート: 3000
  • メモリ: 512 MiB(コスト効率と性能のバランス)
  • CPU: 1
  • 最大リクエスト数: 80(同時処理数)
  • タイムアウト: 60秒

スケーリング設定:

  • 最小インスタンス: 0(リクエストがない時はシャットダウン)
  • 最大インスタンス: 10

変数とシークレット:

環境変数:

DIFY_API_URL = https://api.dify.ai/v1

(カスタムDifyインスタンスを使用している場合は、適宜URLを変更してください)

シークレット参照(3つ追加):

  1. line-access-token → 環境変数 LINE_CHANNEL_ACCESS_TOKEN
  2. line-channel-secret → 環境変数 LINE_CHANNEL_SECRET
  3. dify-api-key → 環境変数 DIFY_API_KEY
  4. 「作成」をクリック

方法2: CLI一括デプロイ(上級者向け)

.env.localがある場合、以下のコマンドで一発デプロイ:

# プロジェクトID設定
export PROJECT_ID="your-project-id"

# Secret Managerに.env.localの値を一括登録
grep LINE_CHANNEL_ACCESS_TOKEN .env.local | cut -d '=' -f2 | xargs -I {} echo -n {} | gcloud secrets create line-access-token --data-file=-
grep LINE_CHANNEL_SECRET .env.local | cut -d '=' -f2 | xargs -I {} echo -n {} | gcloud secrets create line-channel-secret --data-file=-
grep DIFY_API_KEY .env.local | cut -d '=' -f2 | xargs -I {} echo -n {} | gcloud secrets create dify-api-key --data-file=-

# IAM権限設定
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')-compute@developer.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"

# ビルド&デプロイ
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/$PROJECT_ID/line-chatbot/server:latest && \
gcloud run deploy line-chatbot-server \
  --image asia-northeast1-docker.pkg.dev/$PROJECT_ID/line-chatbot/server:latest \
  --platform managed \
  --region asia-northeast1 \
  --allow-unauthenticated \
  --set-env-vars "DIFY_API_URL=$(grep DIFY_API_URL .env.local | cut -d '=' -f2)" \
  --set-secrets "LINE_CHANNEL_ACCESS_TOKEN=line-access-token:latest,LINE_CHANNEL_SECRET=line-channel-secret:latest,DIFY_API_KEY=dify-api-key:latest" \
  --memory 512Mi \
  --cpu 1 \
  --timeout 60s \
  --max-instances 10 \
  --concurrency 80 \
  --port 3000

デプロイ後の設定

LINE Developers ConsoleでWebhook URL更新

デプロイ完了後、Cloud RunのURLが表示されます:

https://line-chatbot-server-xxxxx-an.a.run.app

LINE Developers Consoleで:

  1. チャネル設定 → Messaging API設定
  2. Webhook URL: https://line-chatbot-server-xxxxx-an.a.run.app/api/line/webhook
  3. 「検証」をクリックして接続確認
  4. Webhookを「有効」に設定

動作確認

  1. LINEでボットを友だち追加
  2. メッセージを送信
  3. Dify AIから応答が返ってくることを確認

ログ確認

# リアルタイムログ
gcloud run services logs tail line-chatbot-server --region asia-northeast1

# 過去のログ(JSON形式)
gcloud logging read "resource.type=cloud_run_revision \
  AND resource.labels.service_name=line-chatbot-server" \
  --limit 50 \
  --format json

コスト試算

前提条件

  • 月間10,000 webhookリクエスト
  • 平均レスポンス時間: 200ms
  • メモリ: 512MB、CPU: 1
  • 最小インスタンス: 0(リクエストがない時はシャットダウン)

推定月額コスト

$0.50 〜 $2.00/月(ほぼ無料枠内)

Cloud Runの無料枠:

  • リクエスト: 200万回/月
  • CPU時間: 18万vCPU秒/月
  • メモリ: 36万GiB秒/月

低トラフィックのLINE Botなら、ほぼ無料で運用可能です。

コールドスタート削減(オプション)

リクエストがない時もインスタンスを1つ常駐させる場合:

gcloud run services update line-chatbot-server \
  --region asia-northeast1 \
  --min-instances 1

追加コスト: 約$5〜7/月 メリット: 初回リクエストの遅延(~300ms)がなくなる

パフォーマンス最適化のポイント

1. standalone出力で起動時間短縮

通常のNext.jsビルド:

  • 起動時に全モジュールを読み込み
  • 不要な依存関係も含まれる

standalone出力:

  • 必要最小限のファイルのみ
  • 起動時間が約40%短縮

2. Alpine Linuxで軽量化

FROM node:20-alpine  # ← Alpine Linux
ベースイメージサイズ
node:20約1GB
node:20-slim約200MB
node:20-alpine約50MB

Alpine Linuxを選んだ理由:

  • 最小限のパッケージ
  • セキュリティ脆弱性が少ない
  • コールドスタート時の転送量が少ない

3. レイヤーキャッシュの活用

# 先にpackage.jsonだけコピー
COPY package.json package-lock.json ./
RUN npm ci

# 後からソースコードをコピー
COPY . .
RUN npm run build

このようにすることで:

  • package.jsonが変更されない限り、npm ciはキャッシュされる
  • ソースコード変更時もnpm installをスキップできる
  • ビルド時間が約50%短縮

セキュリティ対策

1. 非rootユーザーで実行

RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

USER nextjs  # ← rootではなくnextjsユーザーで実行

なぜ重要?

  • コンテナが侵害されても、ホストへの影響を最小化
  • セキュリティベストプラクティス

2. Secret Managerで機密情報管理

悪い例(環境変数に直接記述):

gcloud run deploy ... \
  --set-env-vars "LINE_CHANNEL_ACCESS_TOKEN=abc123..."

良い例(Secret Manager使用):

gcloud run deploy ... \
  --set-secrets "LINE_CHANNEL_ACCESS_TOKEN=line-access-token:latest"

Secret Managerの利点:

  • 暗号化保存
  • アクセス監査ログ
  • バージョン管理
  • ローテーション容易

3. 最小限の攻撃対象面

  • Alpine Linux(最小限のパッケージ)
  • マルチステージビルド(ビルドツールを含めない)
  • 定期的なnpm audit実行

トラブルシューティング

Cloud Buildトリガーのブランチエラー

このサービスの Cloud Build トリガーは正常に作成されましたが、
構成されたブランチ パターンに一致するブランチが見つかりませんでした。

原因: トリガーがmainブランチを監視しているが、実際はmasterブランチ

解決方法1: トリガー修正

gcloud builds triggers update TRIGGER_NAME \
  --branch-pattern="^master$"

解決方法2: 手動デプロイ(トリガー不要)

gcloud builds submit --tag asia-northeast1-docker.pkg.dev/PROJECT_ID/line-chatbot/server:latest

コンテナ起動失敗

症状: Cloud Runで「Container failed to start」

診断方法:

gcloud logging read "resource.type=cloud_run_revision" --limit 50

よくある原因:

  • 環境変数の欠落 → Secret Managerの設定確認
  • ポート設定ミス → PORT=3000を確認
  • メモリ不足 → 512Mi → 1Giに増量

署名検証エラー

症状: LINEからのWebhookで「Invalid signature」

原因: LINE_CHANNEL_SECRETが間違っている

確認方法:

# Secret Managerの値を確認
gcloud secrets versions access latest --secret="line-channel-secret"

まとめ

実現できたこと

  • Next.js 16アプリケーションをDocker化(イメージサイズ72MB)
  • Google Cloud Runに自動デプロイ
  • Secret Managerで安全なシークレット管理
  • 月額$0.50〜$2.00の低コスト運用
  • 自動スケーリング(0〜10インスタンス)

学んだこと

  1. standalone出力は必須 – Next.jsのDocker化には欠かせない
  2. マルチステージビルドは効果的 – イメージサイズを60%削減
  3. Secret Managerは使うべき – 環境変数に直接書くのは危険
  4. tsconfig.jsonは除外しない – パスエイリアス解決に必要
  5. Alpine Linuxは軽量だが、注意も必要 – ネイティブモジュールがある場合は要確認

Next.jsのDocker化Tips

項目推奨理由
出力モードstandaloneイメージサイズ60%削減
ベースイメージnode:20-alpine軽量・セキュア
ビルド方式マルチステージ最終イメージに不要ファイルなし
ユーザー非root(nextjs)セキュリティ強化
tsconfig.json含めるパスエイリアス必須

次のステップ

今後実装したい機能:

  • CI/CDパイプライン構築(GitHub Actions)
  • カナリアデプロイ設定
  • Cloud Monitoringでアラート設定
  • カスタムドメイン設定
  • Cloud CDN統合

参考リンク


執筆者について

LINE Bot × Dify AIで業務自動化を実現するWebhookサーバーを開発・運用中。VercelからCloud Runへの移行で、より柔軟な運用体制を構築しました。

この記事が参考になったら、ぜひスターをお願いします

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です