Use Cases (更新: 2026/6/2)

Claude CodeでGCP Cloud Runへデプロイする実践ガイド

Cloud RunでNode.js APIを安全に公開する手順を、Claude Code活用込みで実装レベルにまとめます。

Claude CodeでGCP Cloud Runへデプロイする実践ガイド

Cloud Runは「コンテナをHTTPサービスとして公開したいが、KubernetesやECSほど運用したくない」場面でかなり強い選択肢です。Cloud FunctionsやLambdaのように関数単位で切り出す必要はなく、ExpressやFastifyで書いた普通のAPIをDockerイメージとして持ち込めます。

この記事では、Claude Codeを使ってNode.js/ExpressサービスをCloud Runへ出す流れを、手元で動かせるコード、Dockerfile、Artifact Registry、gcloud run deploy、サービスアカウント、Secret Manager、ログ、ロールバック、コストとセキュリティの落とし穴まで一気通貫で整理します。

前提の用語を平たく言うと、Cloud Runは「リクエストが来たらコンテナを起動して処理するGCPのサーバーレス実行基盤」です。Artifact Registryは「Dockerイメージ置き場」、Secret Managerは「パスワードやAPIキーの保管庫」、IAMは「誰が何をできるかを決める権限表」です。

Cloud Runが勝つ場面

Cloud RunはCloud FunctionsやLambdaの完全な上位互換ではありません。短いイベント処理だけならCloud FunctionsやLambdaの方が軽いこともあります。ただし、次のようなケースではCloud Runの方が実装と運用のバランスがよくなります。

用途Cloud Runが向く理由
Webhook受信用APIStripe、GitHub、LINEなどのHTTP POSTをExpressで自然に扱える
小さなBFF/APIサーバールーティング、認証、ミドルウェアを普通のNode.jsアプリとして書ける
バッチ起動用HTTPエンドポイントCloud SchedulerからHTTPで呼び、ジョブ化しすぎずに運用できる
AI/LLMの軽い推論API依存ライブラリやネイティブバイナリをコンテナに閉じ込められる

逆に、1回の処理が数十ミリ秒で終わる単純なイベントハンドラ、長時間常駐ワーカー、GPUを重く使う推論基盤は別サービスも検討します。Cloud RunはHTTPサービスとしては扱いやすいですが、常時バックグラウンドで回る処理はCPU割り当て設定とコストを必ず見ます。

最小のExpressサービス

まずCloud Runで動く最小構成を作ります。Cloud Runは起動時にPORT環境変数を渡すので、固定値ではなくprocess.env.PORTを使います。/healthはデプロイ後の疎通確認用です。

{
  "name": "cloud-run-claude-code-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "test": "node --test"
  },
  "dependencies": {
    "express": "^4.19.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^22.10.0",
    "tsx": "^4.19.2",
    "typescript": "^5.7.2"
  }
}
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"]
}
import express from "express";

const app = express();
app.use(express.json());

const requiredEnv = ["DATABASE_URL", "JWT_SECRET"];
for (const key of requiredEnv) {
  if (!process.env[key]) {
    console.error(`Missing required environment variable: ${key}`);
    process.exit(1);
  }
}

app.get("/health", (_req, res) => {
  res.status(200).json({ ok: true, service: "myapp-api" });
});

app.post("/webhooks/example", (req, res) => {
  console.log("webhook_received", {
    eventType: req.body?.type ?? "unknown",
    receivedAt: new Date().toISOString()
  });
  res.status(202).json({ accepted: true });
});

app.get("/config-check", (_req, res) => {
  res.json({
    nodeEnv: process.env.NODE_ENV ?? "development",
    hasDatabaseUrl: Boolean(process.env.DATABASE_URL),
    hasJwtSecret: Boolean(process.env.JWT_SECRET)
  });
});

const port = Number(process.env.PORT ?? 8080);
const server = app.listen(port, () => {
  console.log(`listening on ${port}`);
});

process.on("SIGTERM", () => {
  console.log("SIGTERM received, closing HTTP server");
  server.close(() => process.exit(0));
  setTimeout(() => process.exit(1), 30000).unref();
});

ローカル確認は次の通りです。Secret Managerを使う前でも、ローカルではダミー値を渡せば起動できます。

npm install
DATABASE_URL="postgresql://local" JWT_SECRET="local-secret" npm run dev
curl http://localhost:8080/health
curl -X POST http://localhost:8080/webhooks/example \
  -H "Content-Type: application/json" \
  -d '{"type":"demo.created"}'

DockerfileをClaude Codeにレビューさせる

Cloud Runはコンテナをそのまま動かすので、Dockerfileの品質が本番品質に直結します。Claude Codeには「軽量化」だけでなく、非rootユーザー、ビルドキャッシュ、不要ファイル除外、PORT対応まで条件として渡します。

claude -p "
Review and improve this Cloud Run Docker setup.
Requirements:
- Node.js 22 LTS, TypeScript, Express
- production dependencies only in runtime image
- run as a non-root user
- listen on the PORT environment variable
- include .dockerignore
- explain any Cloud Run security or cost risks
Return the final Dockerfile and a short review checklist.
"
FROM node:22-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY tsconfig.json ./
COPY src ./src
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=8080

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist

USER appuser
EXPOSE 8080
CMD ["node", "--max-old-space-size=384", "dist/index.js"]
node_modules
dist
.env
.env.*
*.log
.git
.gitignore
Dockerfile
README.md

Dockerイメージもローカルで確認します。

docker build -t myapp-api:local .
docker run --rm -p 8080:8080 \
  -e PORT=8080 \
  -e DATABASE_URL="postgresql://local" \
  -e JWT_SECRET="local-secret" \
  myapp-api:local
curl http://localhost:8080/health

Artifact Registryと初回デプロイ

Artifact RegistryはDockerイメージの保管先です。Dockerでpushする前に、Google Cloud CLIの認証ヘルパーを対象リージョンへ設定します。公式ドキュメントでもgcloud auth configure-docker HOSTNAME-LISTが案内されています。

PROJECT_ID="my-project-123"
REGION="asia-northeast1"
REPOSITORY="myapp"
SERVICE="myapp-api"
IMAGE="$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/api:v1.0.0"

gcloud config set project "$PROJECT_ID"
gcloud services enable run.googleapis.com artifactregistry.googleapis.com secretmanager.googleapis.com logging.googleapis.com

gcloud artifacts repositories create "$REPOSITORY" \
  --repository-format=docker \
  --location="$REGION" \
  --description="Docker images for myapp"

gcloud auth configure-docker "$REGION-docker.pkg.dev"
docker build -t "$IMAGE" .
docker push "$IMAGE"

Cloud Runへの初回デプロイでは、まずサービスアカウントを明示します。デフォルトサービスアカウントに広い権限を持たせたままにすると、あとで権限境界がわかりにくくなります。

gcloud iam service-accounts create myapp-run \
  --display-name="Cloud Run runtime for myapp"

SERVICE_ACCOUNT="myapp-run@$PROJECT_ID.iam.gserviceaccount.com"

gcloud run deploy "$SERVICE" \
  --image "$IMAGE" \
  --region "$REGION" \
  --platform managed \
  --service-account "$SERVICE_ACCOUNT" \
  --memory 512Mi \
  --cpu 1 \
  --concurrency 80 \
  --min-instances 0 \
  --max-instances 20 \
  --allow-unauthenticated \
  --set-env-vars NODE_ENV=production \
  --port 8080

未認証公開が不要な社内APIなら、--allow-unauthenticatedを外してCloud Run Invoker権限を付与します。公開APIでも、認証、署名検証、レート制限、Cloud Armorやロードバランサーの要否は別途レビューします。

Secret ManagerとIAM

Secret Managerは、DB接続文字列やJWT署名鍵を安全に渡すために使います。平文の--set-env-vars DATABASE_PASSWORD=...は、履歴や設定画面に残りやすいため避けます。

echo -n "postgresql://user:password@host:5432/app" | \
  gcloud secrets create DATABASE_URL --data-file=-

echo -n "replace-with-long-random-value" | \
  gcloud secrets create JWT_SECRET --data-file=-

gcloud secrets add-iam-policy-binding DATABASE_URL \
  --member="serviceAccount:$SERVICE_ACCOUNT" \
  --role="roles/secretmanager.secretAccessor"

gcloud secrets add-iam-policy-binding JWT_SECRET \
  --member="serviceAccount:$SERVICE_ACCOUNT" \
  --role="roles/secretmanager.secretAccessor"

gcloud run services update "$SERVICE" \
  --region "$REGION" \
  --set-secrets "DATABASE_URL=DATABASE_URL:latest,JWT_SECRET=JWT_SECRET:latest"

ここでのIAMは最小権限に寄せます。プロジェクト全体へroles/editorを付けるのではなく、必要なシークレット単位でroles/secretmanager.secretAccessorを付ける方が事故範囲を狭くできます。

Concurrency、min instances、コスト

Concurrencyは「1つのコンテナインスタンスが同時に処理できるリクエスト数」です。デフォルトのままでも動きますが、DB接続数や外部API制限と合わせて決めないと、1インスタンスにリクエストを詰め込みすぎます。

gcloud run services update "$SERVICE" \
  --region "$REGION" \
  --concurrency 40 \
  --min-instances 1 \
  --max-instances 20 \
  --cpu-throttling

min-instances 1はコールドスタート対策になりますが、アイドル中も課金対象です。夜間アクセスがほぼない個人開発や検証環境では0、業務APIやWebhookで初回遅延を避けたい本番では1以上、というように環境ごとに分けます。

3つの実用ケースで考えると、Webhook受信はconcurrency 20-40で署名検証とDB書き込みを安定させます。BFF/APIは40-80から始め、p95レイテンシとDB接続数を見ます。軽いステータスAPIは80以上でもよいですが、外部API呼び出しが詰まるなら下げます。

Cloud Loggingとロールバック

Cloud Runのリクエストログ、コンテナログ、システムログはCloud Loggingに送られます。アプリ側は標準出力に構造化しやすいログを出せば、まずは十分です。

gcloud run services logs read "$SERVICE" \
  --region "$REGION" \
  --limit 20

gcloud logging read \
  "resource.type=cloud_run_revision AND resource.labels.service_name=$SERVICE" \
  --limit 20 \
  --format=json

デプロイで不具合が出たら、Cloud Runのリビジョンにトラフィックを戻します。リビジョンは設定変更やデプロイごとに作られる不変の版です。

gcloud run revisions list \
  --service "$SERVICE" \
  --region "$REGION"

gcloud run services update-traffic "$SERVICE" \
  --region "$REGION" \
  --to-revisions myapp-api-00012-abc=100

Claude Codeには、デプロイ前レビューを専用プロンプトで依頼します。

claude -p "
Act as a Cloud Run deployment reviewer.
Review package.json, Dockerfile, src/index.ts, and the gcloud commands below.
Find blockers before production:
- Cloud Run PORT handling
- SIGTERM graceful shutdown
- non-root container
- Secret Manager usage
- service account and IAM least privilege
- concurrency, min instances, max instances, and cost risks
- Cloud Logging observability
- rollback command for the previous revision
Return: critical issues, recommended fixes, and commands to verify after deploy.
"

具体的な落とし穴

1つ目はCloud FunctionsやLambdaの気分で巨大な依存を詰めることです。Cloud Runはコンテナなので自由度は高いですが、イメージが大きいほどビルド、push、コールドスタートが重くなります。

2つ目はSecret Managerを使っているつもりで、実は--set-env-varsに秘密値を書いていることです。設定履歴やCIログに残る可能性があるため、--set-secretsを使います。

3つ目はmin-instancesの入れっぱなしです。検証環境で1以上にすると、アクセスがなくても費用が積み上がります。

4つ目はConcurrencyを上げすぎることです。Node.js自体は同時接続に強くても、DBコネクションプールや外部SaaSのレート制限が先に詰まります。

5つ目はロールバック手順を本番後に調べることです。gcloud run revisions listupdate-trafficは、平時に一度試しておきます。

公式ドキュメントと内部リンク

Cloud Runは、コンテナ化したHTTPサービスを短時間で公開したいときに、Cloud Functions/Lambdaより自由で、ECS/Kubernetesより軽い中間解になります。Claude Codeには「作って」だけでなく「デプロイ前に壊れる点をレビューして」と頼むと、Dockerfile、IAM、Secret Manager、ログ、ロールバックまで確認の粒度が上がります。

導入支援やチーム向けの運用ルール化が必要なら、Claude Code研修・導入相談も用意しています。

実際に試した結果

この記事のサンプルは、ローカルでnpm run dev、Dockerで/health疎通、Cloud Run想定のPORTと必須環境変数チェックまで確認しました。本番投入前には、実プロジェクトのArtifact Registry権限、Secret Managerのシークレット単位IAM、Cloud Loggingのログ確認、update-trafficによるロールバック演習までをチェックリスト化するのが現実的です。

#claude-code #gcp #cloud-run #docker #typescript #serverless
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。