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.

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

| Pieza | Público | Privado |
|---|---|---|
| Webhook Telegram/Slack | sí, HTTPS | no |
/health | sí (o IP allowlist) | opcional |
| Postgres / Redis | no | Railway internal / Fly 6PN |
| Worker → Worker | no | Service binding |
| Admin / metrics | no | VPN o SSH |
Errores comunes

| Síntoma | Causa | Fix |
|---|---|---|
| Shodan ve :5432 | TCP público en Railway/Fly | quita public networking de la DB |
| Preview tira prod | DATABASE_URL en All envs | URL interna solo Production |
| Worker timeout a la DB | Postgres en VPS sin ruta | Tunnel / Hyperdrive, no 0.0.0.0 |
psql desde el café funciona | “para debug” | cierra; usa fly proxy / railway connect |
| Health 200 y data leak | health interno publicado | health no lista secrets ni dumps |
Relación con el resto
- Llaves: secretos.
- Hostname del bot: TLS.
- Una réplica de escritura: concurrencia.
- Backup del volumen: backup.
Checklist
- Postgres/Redis sin hostname público
-
DATABASE_URLes*.internal/ binding, no un IP de internet - Preview no usa la URL de prod
- No puedes
psqldesde 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) sí 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.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



