Guía9 min

Red privada para un agente IA: Postgres y Redis fuera de internet

Resumen

Cómo dejar el webhook público y la base oculta: private networking de Railway y Fly (IPv6 6PN), Service Bindings en Workers y Secure Compute de Vercel. El agente habla con el chat por HTTPS; la base no tiene hostname público.

RailwayCloudflareVercel
Webhook público y base de datos solo en red privada junto a un agente

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

El webhook tiene que ser público. Postgres no. Si DATABASE_URL es un host que resuelve en internet, un leak de env es un dump. Red privada: el agente llega a Redis/Postgres por DNS interno; no hay puerto 5432 en el mundo. Fuentes oficiales consultadas el 3 de septiembre de 2026.

Secretos cubren la llave. Dominio custom cubre el hostname del bot. Esta guía cubre qué no tiene hostname público.

La regla

Público: HTTPS del webhook / health. Privado: base, cola, métricas internas. Si puedes psql desde tu laptop al host de prod sin VPN, está mal.

Railway

Private networking da DNS interno entre servicios del mismo environment del proyecto: SERVICE_NAME.railway.internal (ejemplo oficial: http://api.railway.internal:PORT). El tráfico va por túnel WireGuard; no hay exposición pública. Cada environment tiene su propia red: el postgres de Production no es el de un PR. El plugin de Postgres no necesita TCP público. Quita el dominio público de la base. El agente usa la URL interna en Production; Preview no hereda esa URL (preview vs prod).

Fly.io

6PN: malla WireGuard IPv6 entre apps de la misma organización. Postgres se habla por .internal (mi-postgres.internal), no por fly.dev. El DNS de Fly (IPv6 fdaa::3) solo devuelve Machines arrancadas en las AAAA; una Machine en autostop no aparece. Las direcciones 6PN no son estáticas: para un host concreto usa <machine_id>.vm.<appname>.internal. El proceso de la base tiene que escuchar en fly-local-6pn (alias en /etc/hosts), no solo en 127.0.0.1. fly scale de la API pública ≠ abrir 5432. Un volumen + una Machine de escritura sigue aplicando (concurrencia); la red privada no te deja dos writers. 6PN salta el Fly Proxy: si necesitas autostop/autostart en el servicio interno, eso es Flycast, no el AAAA crudo.

Workers

No hay VPC clásica. Service bindings llaman a otro Worker sin internet (env.API.fetch). KV/D1/Queues son bindings, no URLs públicas. Si el agente en Workers habla con un Postgres en un VPS, ese Postgres no debe escuchar 0.0.0.0:5432: Cloudflare Tunnel / Hyperdrive / un proxy, no un port-forward.

Vercel

Secure Compute (Enterprise) mete Functions en una red compartida hacia tu VPC. En Hobby/Pro el patrón barato es: no poner la base en Vercel. El agente en Function llama a un host que ya está privado (Railway/Fly/Neon con allowlist). No abras 5432 “un rato”.

Tabla

Webhook público y datastore solo en red interna

PiezaPúblicoPrivado
Webhook Telegram/Slacksí, HTTPSno
/healthsí (o IP allowlist)opcional
Postgres / RedisnoRailway internal / Fly 6PN
Worker → WorkernoService binding
Admin / metricsnoVPN o SSH

Errores comunes

Base expuesta en un hostname público

SíntomaCausaFix
Shodan ve :5432TCP público en Railway/Flyquita public networking de la DB
Preview tira prodDATABASE_URL en All envsURL interna solo Production
Worker timeout a la DBPostgres en VPS sin rutaTunnel / Hyperdrive, no 0.0.0.0
psql desde el café funciona“para debug”cierra; usa fly proxy / railway connect
Health 200 y data leakhealth interno publicadohealth no lista secrets ni dumps

Relación con el resto

Checklist

  • Postgres/Redis sin hostname público
  • DATABASE_URL es *.internal / binding, no un IP de internet
  • Preview no usa la URL de prod
  • No puedes psql desde fuera sin proxy
  • Webhook sigue en HTTPS público
  • Ensayo: desde tu casa el puerto 5432 no responde

FAQ

¿Neon/Supabase públicos con SSL? Mejor que 5432 abierto en un VPS. Sigue siendo internet. Allowlist de IPs del runtime si el proveedor lo da. Railway aísla por environment: no copies el DATABASE_URL interno de prod al PR.

¿Tailscale? Válido en VPS. No lo mezcles con 6PN “por si acaso”: una malla. En Fly, fly proxy o un peer WireGuard a la 6PN sustituye el psql desde el café.

¿Health privado? Sí, si el uptime check corre en la misma red. Si usas un ping externo, el health es público y tonto (no datos).

Qué no es esta guía

No es un tutorial de Tailscale ni de Cloudflare Tunnel paso a paso. Es el criterio: superficie pública = webhook. El resto entra por DNS interno, binding o proxy de un solo salto. Si tu “red privada” es un security group con 0.0.0.0/0, no es privada.

Un agente con tools que pegan a APIs de terceros (OpenAI, Telegram) sale a internet. Eso no justifica que Postgres salga. Egress ≠ ingress. Cierra ingress de la base; deja egress del proceso.

Compose en una VPS: postgres sin ports: en el compose. Solo expose a la red del bridge. Publicar 5432:5432 “para DBeaver” es el incidente. Usa ssh -L o el proxy del proveedor.

El ensayo: nc -vz <db-host> 5432 desde fuera. Timeout. Desde el contenedor del agente: conexión ok. En Railway el host de prueba es SERVICE_NAME.railway.internal; en Fly, app.internal o <id>.vm.app.internal si apuntas a una Machine concreta. Si el nc externo responde, la base sigue pública: el DNS interno no “oculta” un puerto que también escuchas en 0.0.0.0.

Siguiente paso: si la red ya es privada y igual se fuga estado, backup y secretos. Sin runtime: curso.