Desplegar en GCP Cloud Run con Claude Code: guía práctica
Publica una API Node.js segura en Cloud Run con Docker, IAM, secretos, logs y rollback.
Cloud Run encaja muy bien cuando quieres publicar un servicio HTTP en contenedor sin operar Kubernetes, ECS ni una flota de máquinas virtuales. Frente a Cloud Functions o Lambda, no te obliga a convertir toda la aplicación en funciones pequeñas. Puedes llevar una API Express normal, empaquetarla como imagen Docker y dejar que Google Cloud gestione HTTPS, revisiones, escalado y logs.
Esta guía muestra cómo usar Claude Code para llevar una API TypeScript/Express desde local hasta Cloud Run. Incluye código ejecutable, Dockerfile, prueba local, Artifact Registry, gcloud run deploy, cuenta de servicio, IAM, Secret Manager, concurrencia, instancias mínimas, Cloud Logging, rollback de revisiones, errores de coste y una prompt de revisión de despliegue.
En términos simples: Cloud Run es una plataforma serverless para ejecutar contenedores que responden a HTTP. Artifact Registry es el almacén de imágenes Docker. Secret Manager es la caja fuerte de contraseñas y claves API. IAM es el sistema de permisos que decide qué identidad puede acceder a qué recurso.
Cuándo Cloud Run gana a Functions o Lambda
Cloud Run no siempre es mejor que Cloud Functions o Lambda. Para un manejador de eventos mínimo, una función puede ser más simple. Cloud Run gana cuando el servicio ya es una aplicación HTTP o cuando necesitas más control del runtime.
| Caso de uso | Por qué Cloud Run encaja |
|---|---|
| Receptor de webhooks | Stripe, GitHub o LINE se modelan de forma natural como rutas Express |
| BFF o API pequeña | Autenticación, middleware, rutas y validación quedan en una app Node.js normal |
| Endpoint HTTP para tareas programadas | Cloud Scheduler puede llamarlo sin crear otra infraestructura de jobs |
| API ligera de IA o datos | Dependencias nativas y binarios personalizados quedan dentro del contenedor |
Para workers de larga duración, revisa CPU y coste antes. Cloud Run es excelente para servicios orientados a petición, no una respuesta automática para bucles en segundo plano.
Servicio Express ejecutable
Cloud Run pasa el puerto mediante la variable PORT. El servicio debe leerla, exponer /health y cerrar correctamente cuando reciba SIGTERM.
{
"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();
});
Compruébalo en local:
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 y revisión con Claude Code
Pide a Claude Code una revisión de producción, no solo generación de archivos. Debe comprobar imagen pequeña, dependencias de producción, usuario no root, .dockerignore, soporte de PORT y riesgos de seguridad o coste.
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 y primer despliegue
Artifact Registry guarda la imagen Docker. Configura autenticación para el host regional, crea el repositorio y sube la imagen.
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"
Crea una cuenta de servicio de runtime antes de desplegar.
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
Si la API es interna, elimina --allow-unauthenticated y concede Cloud Run Invoker solo a los llamadores necesarios.
Secret Manager, IAM y seguridad
No escribas secretos en --set-env-vars. Guárdalos en Secret Manager e inyéctalos en Cloud Run.
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"
Prefiere IAM por secreto en lugar de permisos amplios de proyecto. Reduce el impacto si una identidad se usa mal.
Concurrencia, instancias mínimas y coste
La concurrencia define cuántas peticiones simultáneas procesa una instancia. Subirla reduce instancias, pero puede saturar la base de datos o una API externa.
gcloud run services update "$SERVICE" \
--region "$REGION" \
--concurrency 40 \
--min-instances 1 \
--max-instances 20 \
--cpu-throttling
Usa min-instances 0 en desarrollo o proyectos con poco tráfico. Usa 1 o más si un webhook o API de producción no tolera arranques en frío. Para webhooks empieza con 20-40; para BFF/API, 40-80, y ajusta con latencia p95, conexiones de base de datos y errores.
Logs y rollback
Cloud Run envía logs de petición, contenedor y sistema a Cloud Logging. Para Node.js suele bastar con escribir en stdout/stderr.
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
Cada despliegue o cambio de configuración crea una revisión inmutable. Revierte moviendo tráfico a una revisión estable.
gcloud run revisions list \
--service "$SERVICE" \
--region "$REGION"
gcloud run services update-traffic "$SERVICE" \
--region "$REGION" \
--to-revisions myapp-api-00012-abc=100
Prompt de revisión antes de producción:
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.
"
Errores habituales
Las imágenes grandes empeoran build, push y arranque en frío. Los secretos en variables normales pueden quedar en historial o logs de CI. min-instances 1 baja la latencia, pero cobra incluso sin tráfico. Una concurrencia demasiado alta puede romper la base de datos o APIs externas. El rollback debe ensayarse antes del incidente.
Referencias y siguiente paso
- Documentación de Cloud Run
- Desplegar imágenes de contenedor en Cloud Run
- Autenticación Docker de Artifact Registry
- Configurar secretos en Cloud Run
- Logging en Cloud Run
- Rollbacks y migración de tráfico en Cloud Run
- Integración Docker con Claude Code
- Guía Claude Code AWS ECS/Fargate
- Buenas prácticas de seguridad con Claude Code
Cloud Run es un punto medio útil: más flexible que una plataforma solo de funciones y más ligero que ECS o Kubernetes. Para convertir prompts, revisiones y reglas de despliegue en un proceso de equipo, revisa formación y consultoría de Claude Code.
Nota de verificación práctica
El ejemplo se comprobó con npm run dev, acceso Docker a /health, manejo de PORT y validación de variables obligatorias. Antes de producción, repite la prueba con permisos reales de Artifact Registry, IAM de Secret Manager, consultas de Cloud Logging y un ensayo de rollback con update-traffic.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.