Arquitectura de un despliegue serverless en Google Cloud Run
Contexto
Una aplicación SaaS real tiene tres frentes vivos a la vez: una web de cara al usuario, una API de negocio y procesos en segundo plano (envíos, recordatorios, programaciones). Llevar eso a producción con un coste optimizado y sin un equipo de plataforma detrás te obliga a apoyarte en servicios gestionados y a automatizar el ciclo de build y despliegue.
En este artículo cuento cómo monté un despliegue así sobre Google Cloud Platform, partiendo de un monorepo con una API (NestJS) y una web (Next.js) que comparten dependencias. Tenía claro lo que quería: cómputo serverless con escalado a cero, datos y caché sin exposición directa a internet, secretos fuera del código y un despliegue reproducible y atado a un commit.
Idea clave: lo que aporta un despliegue serverless no es solo "que funcione en cloud", sino que cada pieza tenga una responsabilidad clara, que el tráfico de datos quede privado y que publicar una versión nueva sea un único comando repetible.
Los servicios y su papel
Cada responsabilidad cae en un servicio gestionado distinto, y todo el tráfico de datos se mantiene dentro de la VPC privada:
- Cloud Run — dos servicios (API en
:4000y web en:3000), públicos, con auth propia por JWT y escalado de 0 a N instancias según tráfico. - Cloud SQL (PostgreSQL 16) — base de datos relacional; Cloud Run se conecta por socket Unix vía el Cloud SQL Auth Proxy integrado, sin abrir un puerto público.
- Memorystore (Redis 7) — caché y backend de las colas de trabajos, accesible solo desde la VPC y con TLS obligatorio.
- Cloud Storage — objetos (avatares, adjuntos, recursos) servidos con signed URLs temporales en lugar de exponer el bucket.
- Vertex AI (Gemini) — capa de IA generativa, autenticada por la service account del servicio, sin gestionar claves.
- Secret Manager — connection strings, secretos JWT y claves de terceros, inyectados como variables de entorno en el deploy.
- Artifact Registry — repositorio Docker privado por región para las imágenes.
- Firebase Hosting — landing estática servida por CDN, separada del panel.
Contenerización
Tanto la API como la web usan Dockerfiles multi-stage sobre node:22-slim, para tener imágenes pequeñas y un arranque rápido. El build se ejecuta desde la raíz del monorepo y se etiqueta por git SHA además de latest, así cualquier revisión en Cloud Run se puede rastrear hasta un commit concreto.
La API incluye Prisma y ejecuta prisma migrate deploy en el entrypoint antes de arrancar. La web usa el modo standalone de Next.js, que produce un servidor autocontenido con solo las dependencias necesarias; las variables NEXT_PUBLIC_* se hornean en tiempo de build, así que viajan dentro de la imagen.
Conexión privada a los datos
Lo que de verdad define la seguridad aquí es cómo llega Cloud Run a la base de datos y a la caché:
- Cloud SQL por socket: al pasar
--add-cloudsql-instances, Cloud Run monta el Cloud SQL Auth Proxy como un socket Unix dentro del contenedor. La aplicación se conecta a ese socket local mediante un túnel autenticado, nunca a una IP pública. - Memorystore por VPC: Redis solo es accesible desde la red privada. Con Direct VPC egress (
private-ranges-only) el contenedor alcanza la IP privada de Redis sin enrutar todo su tráfico por la VPC ni añadir un connector serverless extra. La conexión va cifrada (rediss://, TLS).
Al final ni la base de datos ni la caché aceptan conexiones directas desde internet.
Secretos y configuración
Las variables sensibles viven en Secret Manager y se inyectan en el deploy como NOMBRE=SECRETO:latest. Rotar un valor es publicar una nueva versión del secreto y redesplegar: el código no cambia, porque las referencias apuntan siempre a :latest. La configuración no sensible (dominio, entorno) va como variables de entorno normales.
Trabajos en segundo plano
Los procesos asíncronos (envíos de email, recordatorios programados) corren con colas de trabajos dentro del propio proceso de la API, usando Memorystore como backend. Mientras el volumen lo permita, esto me ahorra levantar un servicio extra, y deja la puerta abierta a sacar un worker dedicado a su propio servicio de Cloud Run cuando haga falta escalar.
El flujo de despliegue
Publicar una versión es el mismo ciclo para los tres frentes:
- Build de la imagen Docker multi-stage desde la raíz del monorepo, etiquetada por
git SHAylatest. - Push de ambas etiquetas a Artifact Registry.
- Deploy en Cloud Run con un único comando que monta los secretos, el socket de Cloud SQL y la salida a la VPC.
- Migraciones Prisma aplicadas por el contenedor en el arranque; la landing se publica aparte con Firebase Hosting.
Resultado
La plataforma queda en producción con dominios propios, TLS gestionado por la propia plataforma, datos y caché sin exposición directa a internet y un despliegue reproducible por servicio. En reposo el coste es prácticamente nulo gracias al escalado a cero, que encaja bien con un producto en evolución donde el tráfico todavía no es constante.
Si me quedo con algo de una arquitectura así no es que arranque, sino que el criterio de diseño —cada servicio en su sitio, tráfico de datos privado, despliegue de un comando— aguante cuando el sistema crece. Es lo que te deja añadir un worker dedicado, subir el mínimo de instancias o mover un servicio sin rehacer todo lo demás.
Siguiente paso
Si quieres ver el detalle de servicios y decisiones, revisa el proyecto de despliegue cloud en GCP o la cheatsheet de Cloud Run & Ops. ¿Tienes que llevar una app a producción y quieres comentarlo? Escríbeme aquí.
¿Aplicamos esta idea en tu contexto?
Si quieres convertir este enfoque en un plan accionable para tu equipo, puedo ayudarte a definir prioridades, riesgos y siguiente iteración.