Guía9 min

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.

Docker
Dos redes Compose: publica para el webhook y privada para Postgres

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

Frontend público y backend interno

Servicio / knobfrontendbackendports hostTecho verificado
Caddyno80/443how-to HOST_PORT
agenteno (o 3000 debug)dual; how-to worker+public
postgresnonointernal ≠ quita ports
default implícito<project>_default
internal: truesin gateway externo
external: trueno crea; solo + name
network_mode + networksyaml rechazado
host + portserror runtime
enable_ipv4: falseCompose ≥ 2.33.1
interface_nameCompose ≥ 2.36.0

Errores comunes

5432 publicado en el host con network internal

SíntomaCausaFix
localhost:5432 desde caféportsquítalos
agent no resuelve postgresno está en backenddual network
internal y Telegram muertoagente solo backendfrontend también
default bridge leftoversno declaraste networksexplícitas
Fly internal: trueno aplica6PN
yaml inválidoexternal + driversolo external+name
Network not foundexternal inexistentedocker network create primero
ports + network_mode: hostspecquita uno
IP hardcodeadaIPAM dinámicanombre de servicio

Relación con el resto

Checklist

  • Network backend internal
  • Postgres sin ports (compose port postgres 5432 no publica)
  • Agente en frontend + backend
  • Hostname postgres no localhost
  • Ensayo: nc 5432 desde el host falla; desde agent funciona
  • Caddy no está en backend
  • No mezclas network_mode y networks en 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.