Deploy to GCP Cloud Run with Claude Code: Practical Guide
Build and deploy a secure Node.js API on Cloud Run with Docker, IAM, secrets, logs, and rollback.
Cloud Run is a strong fit when you want to publish a containerized HTTP service without running Kubernetes, ECS, or a full VM fleet. Compared with Cloud Functions or Lambda, you do not have to reshape everything into tiny functions. You can bring a normal Express or Fastify service, package it as a Docker image, and let Google Cloud handle HTTPS, revisions, scaling, and logs.
This guide shows how I would use Claude Code to move a TypeScript/Express API from local development to Cloud Run. It covers runnable code, Dockerfile review, local testing, Artifact Registry, gcloud run deploy, service accounts, IAM, Secret Manager, concurrency, minimum instances, Cloud Logging, revision rollback, cost traps, and a deployment-review prompt.
Plain terms first: Cloud Run is a serverless platform for running containers that respond to HTTP requests. Artifact Registry is the Docker image store. Secret Manager is the vault for passwords and API keys. IAM is the permission system that decides which identity can access which resource.
When Cloud Run Beats Functions or Lambda
Cloud Run is not always better than Cloud Functions or Lambda. For a tiny event handler with no custom runtime needs, a function can be simpler. Cloud Run wins when the service is already an HTTP app or when the runtime needs are too specific for a function package.
| Use case | Why Cloud Run fits |
|---|---|
| Webhook receiver | Stripe, GitHub, and messaging webhooks map naturally to Express routes |
| Small BFF or API server | Auth, middleware, routing, and validation stay in one normal Node.js app |
| Scheduled HTTP batch endpoint | Cloud Scheduler can call the service without creating a separate job framework |
| Lightweight AI or data API | Native dependencies and custom binaries stay inside the container |
Avoid forcing Cloud Run into long-running background workers unless you have reviewed CPU allocation and cost. It is excellent for request-driven services; always-on workers need more care.
Runnable Express Service
Cloud Run injects the listening port through the PORT environment variable. The service should read that value, expose a health endpoint, and shut down cleanly when Cloud Run sends SIGTERM during scale-in or deployments.
{
"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();
});
Run it locally before touching Google Cloud:
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 and Claude Code Review
Ask Claude Code for a production review, not just file generation. The important points are a small runtime image, production dependencies only, non-root execution, .dockerignore, PORT, and Cloud Run cost/security risks.
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 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 and First Deploy
Artifact Registry stores the Docker image. Configure Docker authentication for the regional host, create the repository, build, and push.
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"
Create a dedicated runtime service account before deploying. This keeps production permissions understandable.
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
For an internal API, remove --allow-unauthenticated and grant the Cloud Run Invoker role only to callers that need it.
Secret Manager and IAM
Do not put secrets in plain --set-env-vars commands. Store them in Secret Manager and let Cloud Run inject them as environment variables.
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"
Prefer secret-level IAM to project-wide editor access. It limits the blast radius if one service identity is misused.
Concurrency, Minimum Instances, and Cost
Concurrency means the maximum number of simultaneous requests one container instance can process. Higher values reduce instance count, but they can overload a database pool or third-party API.
gcloud run services update "$SERVICE" \
--region "$REGION" \
--concurrency 40 \
--min-instances 1 \
--max-instances 20 \
--cpu-throttling
Use min-instances 0 for development and low-traffic hobby services. Use 1 or more when a production webhook or API cannot tolerate cold-start delay. Start webhook receivers around 20-40, BFF APIs around 40-80, and very lightweight status APIs higher only after checking latency and downstream limits.
Logs and Rollback
Cloud Run sends request logs, container logs, and system logs to Cloud Logging. Writing to stdout or stderr is enough for most Node.js services.
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
Every deploy or configuration change creates an immutable revision. Roll back by moving traffic to a known-good revision.
gcloud run revisions list \
--service "$SERVICE" \
--region "$REGION"
gcloud run services update-traffic "$SERVICE" \
--region "$REGION" \
--to-revisions myapp-api-00012-abc=100
Use this deployment-review prompt before production:
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.
"
Pitfalls to Catch Early
Large images slow builds, pushes, and cold starts. Plain environment variables leak secrets into command history or CI logs. Minimum instances reduce latency but keep billing active. High concurrency can hide downstream bottlenecks until production. Rollback commands should be tested before an incident, not during one.
References and Next Steps
- Cloud Run documentation
- Deploy container images to Cloud Run
- Artifact Registry Docker authentication
- Configure secrets for Cloud Run
- Cloud Run logging
- Cloud Run rollbacks and traffic migration
- Claude Code Docker integration
- Claude Code AWS ECS/Fargate guide
- Claude Code security best practices
Cloud Run gives you a practical middle ground: more flexible than function-only platforms, lighter to operate than ECS or Kubernetes. If you want team enablement around prompts, review checklists, and deployment guardrails, see Claude Code training and consulting.
Hands-On Verification Note
The sample was checked with local npm run dev, Docker /health access, PORT handling, and required environment validation. Before production, repeat the checks with your real project permissions, Secret Manager IAM, Cloud Logging queries, and a rollback drill using update-traffic.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.