Redes Compose para un agente IA: postgres fuera del default bridge
Resumen
Cómo no dejar la DB en el bridge default: networks internas, el agente en frontend+backend, postgres solo backend. Distinto de Fly 6PN. internal no bloquea ports. Techos oficiales Compose spec y how-to, verificados con curl el 3 de septiembre de 2026.

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 default bridge mezcla Caddy, el agente y Postgres. Un ports: 5432:5432 “para DBeaver” abre el host. Red privada es el criterio. Aquí el yaml: dos networks. Fuentes oficiales consultadas el 3 de septiembre de 2026.
La regla
frontend: Caddy + agente (publicado 80/443).
backend internal: true: agente + postgres. Postgres sin ports.
El agente está en las dos. Postgres solo en backend. DNS: postgres:5432 desde el agente, no localhost.
El spec top-level (curl 2026-09-03): declarar la red no la monta en nadie. Hay que grant por servicio con services.<name>.networks. El how-to de Compose lo dice igual: cada servicio lista nombres que apuntan al bloque top-level.
Yaml mínimo
networks:
frontend:
backend:
internal: true
services:
caddy:
networks: [frontend]
ports: ["80:80", "443:443"]
agent:
networks: [frontend, backend]
postgres:
networks: [backend]
internal: true (spec): por defecto Compose da conectividad externa a las redes; con true la red queda aislada del exterior (sin gateway por defecto, how-to: “no default gateway for external connectivity”). El agente igual necesita frontend para Telegram.
El how-to aclara: un servicio en internal y en una red no-internal (el worker del ejemplo oficial) sí sale a internet por la red pública. Dual network no es un bug; es el patrón.
Default vs explícitas
Si no declaras networks, Compose crea <project-name>_default (bridge) y mete todos los servicios. El spec: un servicio sin networks equivale a networks: { default: {} } más networks: { default: {} } top-level.
El nombre del proyecto sale del directorio, --project-name o COMPOSE_PROJECT_NAME. No hardcodes myapp_default en runbooks.
Puedes customizar default (spec): name: (tal cual, sin prefijo de proyecto) y driver_opts, p. ej. com.docker.network.bridge.host_binding_ipv4: 127.0.0.1. Eso no sustituye quitar ports de postgres.
ports vs networks
internal no bloquea ports: en el host. Si publicas 5432, el host lo sirve. Quita ports de postgres. Debug: docker compose exec agent psql.
How-to: comunicación servicio-a-servicio usa el CONTAINER_PORT. El HOST_PORT solo vale desde fuera de la red. Ejemplo oficial: postgres://db:5432 entre contenedores; localhost:8001 en el host si publicaste 8001:5432.
docker compose port db 5432 (how-to) te dice el mapeo. Si ves 0.0.0.0:5432, la DB está en el mundo. El ensayo del checklist es el inverso: ese comando no debe devolver nada útil para postgres.
DNS, IPs y aliases
How-to: las IPs se asignan cada start y no persisten. Siempre el nombre del servicio. Tras compose up que recrea, la IP cambia; conexiones viejas se cierran; el cliente debe resolver de nuevo.
POSTGRES_HOST=postgres. No 127.0.0.1. Spec aliases: hostname extra por red. El mismo servicio puede llamarse database en backend y otra cosa en admin. Un alias compartido entre contenedores: no hay garantía de a cuál resuelve.
interface_name (Compose v2.36.0): fuerza el nombre de interfaz (eth0) al unirse a una red. Raro en un bot; no lo copies “por si acaso”.
ipv4_address estático: el spec exige ipam.config.subnet en la red que cubra esa IP. Sin IPAM, Compose no adivina. En un agente con un solo postgres, DNS basta.
enable_ipv4: false (Compose v2.33.1) + enable_ipv6: true: red solo v6. Si Node bindea v4 y el DNS da AAAA, falla. No lo actives sin probar compose exec agent getent hosts postgres.
network_mode vs networks
Spec: si hay network_mode, networks no está permitido — Compose rechaza el yaml. Valores definidos: none, host, service:{name}, container:{name}.
How-to host: comparte el stack del host. Sin port mapping y sin DNS de nombres de servicio. Ve todo el tráfico del host. No lo uses en el webhook. Spec: ports + network_mode: host = error en runtime.
none: cero red. Un one-shot de profile que no habla con nadie puede; migrate contra postgres no.
external, name, attachable
external: true (spec): Compose no crea la red; si falta, error. Cualquier atributo distinto de name invalida el yaml. How-to: créala antes con docker network create o Network not found.
name: se usa tal cual, sin scope de proyecto. Útil con external + ${NETWORK_ID}.
attachable: true: contenedores standalone (no-Compose) pueden unirse. Overlay en Swarm, dice el how-to, se crea attachable; puedes poner false. Un VPS bridge no lo necesita.
Un external compartido entre dos stacks: el how-to lo muestra (inter-project). Si el otro stack baja, el hostname sigue resolviendo o no según qué contenedor quede. Documenta el dueño.
Fly / Railway
No es Compose networks. 6PN / private networking. No copies internal: true a fly.toml. Criterio: red privada.
Tabla

| Servicio / knob | frontend | backend | ports host | Techo verificado |
|---|---|---|---|---|
| Caddy | sí | no | 80/443 | how-to HOST_PORT |
| agente | sí | sí | no (o 3000 debug) | dual; how-to worker+public |
| postgres | no | sí | no | internal ≠ quita ports |
| default implícito | — | — | — | <project>_default |
internal: true | — | sí | — | sin gateway externo |
external: true | — | — | — | no crea; solo + name |
network_mode + networks | — | — | — | yaml rechazado |
host + ports | — | — | — | error runtime |
enable_ipv4: false | — | — | — | Compose ≥ 2.33.1 |
interface_name | — | — | — | Compose ≥ 2.36.0 |
Errores comunes

| Síntoma | Causa | Fix |
|---|---|---|
localhost:5432 desde café | ports | quítalos |
| agent no resuelve postgres | no está en backend | dual network |
| internal y Telegram muerto | agente solo backend | frontend también |
| default bridge leftovers | no declaraste networks | explícitas |
Fly internal: true | no aplica | 6PN |
| yaml inválido | external + driver | solo external+name |
Network not found | external inexistente | docker network create primero |
ports + network_mode: host | spec | quita uno |
| IP hardcodeada | IPAM dinámica | nombre de servicio |
Relación con el resto
- Criterio: red privada.
- Arranque: depends_on — la red no espera SQL.
- Bind: 0.0.0.0.
- TLS: dominio.
Checklist
- Network
backendinternal - Postgres sin
ports(compose port postgres 5432no publica) - Agente en frontend + backend
- Hostname
postgresno localhost - Ensayo:
nc5432 desde el host falla; desde agent funciona - Caddy no está en backend
- No mezclas
network_modeynetworksen el mismo servicio
FAQ
¿Un solo network internal y Caddy en el host? Entonces el agente no llega a Telegram. Dual.
¿network_mode: host? Tira DNS de Compose y publica todo. No.
¿K8s NetworkPolicy? Equivalente más fino; no es este yaml.
¿links:? How-to: alias extra; no hacen falta para hablar por nombre de servicio.
¿extra_hosts / host-gateway? Para un hostname que Docker DNS no registra (staging fijo). No sustituye postgres como servicio.
El ensayo: desde el Mac nc -vz localhost 5432 timeout. compose exec agent nc postgres 5432 ok. docker network inspect lista quién está en backend.
El default bridge es el imán de “funciona en mi laptop” con 5432 publicado. Declara networks en el mismo PR que quitas ports.
Caddy en frontend termina TLS; el agente en 3000 solo en Docker DNS, no en 0.0.0.0 del host si no hace falta.
Siguiente paso: si la red ya es interna y igual hay leak, secretos. Sin runtime: curso.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



