Redes Docker en un VPS: bridge, host y macvlan explicados

13 min de lectura·Matthieu·vpsipv6docker-composenetworkingdocker|

Cómo funcionan las redes bridge, host y macvlan de Docker en un VPS. Resolución DNS en bridge personalizado, publicación de puertos, configuración IPv6 y aislamiento de red con Docker Compose.

Tiene un VPS. Varios contenedores necesitan comunicarse entre sí y con el exterior. Docker ofrece tres controladores de red (network drivers) relevantes: bridge, host y macvlan. Cada uno hace compromisos diferentes entre aislamiento, rendimiento y comodidad.

Este artículo explica cuándo elegir cada controlador, cómo conectar contenedores mediante redes bridge personalizadas y Docker Compose, y cómo evitar los errores habituales que dejan servicios inaccesibles o expuestos accidentalmente.

Requisitos previos: conocimientos básicos de la CLI de Docker y familiaridad con Docker Compose.

¿Cuál es la diferencia entre las redes bridge y host de Docker?

La red bridge asigna a cada contenedor su propio espacio de nombres de red (network namespace) con un par ethernet virtual (veth) que lo conecta a un bridge de software en el host. Los contenedores reciben IPs privadas (normalmente en el rango 172.17.0.0/16) y acceden al exterior a través de NAT gestionado por iptables. La red host elimina el espacio de nombres de red por completo: el contenedor comparte la pila de red del host, vinculándose directamente a los puertos del host sin traducción de direcciones.

Comparación rápida:

Criterio Bridge personalizado Host Macvlan
Aislamiento de red Completo (namespace propio) Ninguno (comparte el host) Completo (dirección MAC propia)
Resolución DNS Automática por nombre de contenedor Usa el DNS del host Sin DNS integrado
Mapeo de puertos Requerido (-p) No necesario (binding directo) No necesario (IP real)
Coste en rendimiento Bajo (NAT + veth) Ninguno Ninguno
Riesgo de seguridad Bajo (aislado por defecto) Alto (sin frontera de red) Medio (requiere modo promiscuo)
Relevancia en VPS Opción principal Monitorización, alto throughput Raramente utilizable

¿Por qué usar una red bridge personalizada en lugar de la bridge por defecto?

La red bridge por defecto (llamada docker0) no ofrece resolución DNS entre contenedores. Los contenedores solo pueden alcanzarse por dirección IP, que cambia con cada reinicio. Las redes bridge personalizadas proporcionan DNS automático, mejor aislamiento y la posibilidad de conectar/desconectar contenedores en caliente. Use una bridge personalizada para todo. Trate la bridge por defecto como un vestigio.

El problema del DNS

Cuando Docker arranca, crea una red bridge por defecto. Cada contenedor sin flag --network explícito termina en ella. Pero los contenedores en la bridge por defecto no pueden resolverse por nombre:

# Start two containers on the default bridge
docker run -d --name web nginx:alpine
docker run -d --name client alpine sleep 3600

# Try name resolution - this fails
docker exec client ping -c1 web
# ping: bad address 'web'

Pruebe lo mismo con una bridge personalizada:

# Create a custom bridge network
docker network create app-net

# Start containers on it
docker run -d --name web2 --network app-net nginx:alpine
docker run -d --name client2 --network app-net alpine sleep 3600

# Name resolution works
docker exec client2 ping -c1 web2
# PING web2 (172.18.0.2): 56 data bytes
# 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.089 ms

Cómo funciona el DNS embebido de Docker

Cada contenedor en una red definida por el usuario recibe 127.0.0.11 como servidor DNS. Es el servidor DNS embebido de Docker. Resuelve nombres de contenedores y alias de servicio a sus direcciones IP actuales. Si el nombre no corresponde a un contenedor, reenvía la consulta a los servidores DNS configurados en el host.

docker exec client2 cat /etc/resolv.conf
# nameserver 127.0.0.11

El servidor DNS corre en el espacio de nombres de red del contenedor, pero la resolución real ocurre en el daemon de Docker en el host. Para evitar conflictos con servicios que puedan usar el puerto 53 dentro del contenedor, el listener DNS de Docker usa internamente un puerto alto aleatorio y redirige las consultas mediante reglas iptables.

No existe una dirección IPv6 equivalente. La dirección 127.0.0.11 funciona incluso en contenedores solo IPv6.

El servidor DNS embebido devuelve un TTL de 600 segundos (10 minutos) para los registros de nombres de contenedores. Esto es relevante para despliegues blue-green: si reemplaza un contenedor, otros pueden seguir resolviendo la IP anterior durante 10 minutos. Las aplicaciones con caché DNS agresivo (Java cachea indefinidamente por defecto) retendrán direcciones obsoletas aún más tiempo.

Los contenedores en la misma red bridge personalizada exponen todos sus puertos entre sí sin necesidad de -p. La publicación de puertos solo controla el acceso desde fuera de la red (el host o internet). Dos contenedores en app-net se comunican libremente por cualquier puerto.

Bridge por defecto vs bridge personalizada

Funcionalidad Bridge por defecto (docker0) Bridge personalizada
DNS por nombre de contenedor No
Conexión/desconexión dinámica No (requiere reinicio del contenedor)
Configurable por red No (requiere editar daemon.json + reiniciar)
Aislamiento de otros stacks No (todos los contenedores sin asignar lo comparten)
Recomendado para producción No

¿Cuándo usar la red host en un VPS?

Use la red host cuando un contenedor necesite rendimiento de red bruto o deba vincularse a muchos puertos dinámicamente. El contenedor comparte directamente la pila de red del host, evitando el NAT y la bridge ethernet virtual. Esto elimina el overhead medible para cargas de alto throughput.

Casos de uso típicos en un VPS:

  • Agentes de monitorización como Prometheus node-exporter o Netdata que necesitan ver todas las interfaces y el tráfico del host
  • Servidores DNS que deben escuchar en la IP real del host
  • Servicios sensibles al rendimiento donde el overhead del NAT importa (tasas de paquetes altas, cargas intensivas de UDP)
# Run a container with host networking
docker run -d --name node-exporter --network host \
  prom/node-exporter:latest

El flag --publish se ignora con la red host. El contenedor se vincula directamente a los puertos del host. Si el puerto 9100 ya está en uso en el host, el contenedor falla al arrancar.

Compromiso de seguridad

La red host ofrece el mismo aislamiento de red que ejecutar el proceso directamente en el host: ninguno. El contenedor puede ver todas las interfaces de red, vincularse a cualquier puerto y capturar tráfico. Úselo solo cuando tenga un motivo concreto.

# Verify which ports a host-networked container opened
ss -tlnp | grep node_exporter
# LISTEN 0 4096 *:9100 *:* users:(("node_exporter",pid=12345,fd=3))

Fíjese bien: esta salida muestra que el proceso escucha en *:9100, es decir, todas las interfaces. Con la red bridge, usted controla esto con el flag -p. Con la red host, la aplicación decide a qué se vincula.

¿Qué es la red macvlan y la necesita en un VPS?

Macvlan asigna a cada contenedor su propia dirección MAC, haciéndolo aparecer como un dispositivo físico separado en la red. El contenedor obtiene una IP real de la subred de su LAN y es accesible directamente sin mapeo de puertos.

En un VPS, casi seguro no necesita macvlan. Las razones:

  • La mayoría de proveedores cloud lo bloquean. Macvlan requiere que la NIC física opere en modo promiscuo. Los hipervisores VPS normalmente bloquean esto a nivel del switch virtual.
  • No hay LAN al que unirse. Su VPS tiene una interfaz pública. Macvlan está diseñado para entornos donde se quiere que los contenedores tengan direcciones propias en una LAN existente, como un laboratorio doméstico con dispositivos IoT o aplicaciones legacy que esperan una IP dedicada.
  • La comunicación host-contenedor se rompe. Por diseño, el kernel de Linux impide que un contenedor macvlan se comunique con el host en el que corre. Se necesitan soluciones alternativas como adjuntar una segunda red bridge.

Si su proveedor de VPS le da un servidor dedicado con acceso completo a la NIC y múltiples IPs, macvlan se vuelve viable. Para despliegues VPS estándar, quédese con las redes bridge personalizadas.

Para referencia, así se crea una red macvlan (probablemente no la necesitará en un VPS):

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  macnet

La opción parent especifica la interfaz del host a la que adjuntarse. El contenedor obtiene su propia IP de la subred y su propia dirección MAC. Otros dispositivos en la LAN pueden alcanzarlo directamente.

¿Cuál es la diferencia entre exponer y publicar un puerto Docker?

EXPOSE en un Dockerfile es documentación. Declara en qué puerto escucha la aplicación. No abre ese puerto al host ni al exterior. --publish (o -p) en tiempo de ejecución crea un mapeo de puerto real del host al contenedor. Sin -p, ningún tráfico externo llega al contenedor.

# In your Dockerfile - this is documentation only
EXPOSE 8080
# This actually maps host:8080 -> container:8080
docker run -d -p 8080:8080 myapp

# This binds to localhost only - external traffic cannot reach it
docker run -d -p 127.0.0.1:8080:8080 myapp

El patrón de enlace 127.0.0.1

Cuando ejecute servicios detrás de un reverse proxy como Nginx, Traefik o Caddy , publique los puertos de contenedores solo en 127.0.0.1. Esto impide el acceso directo desde internet, forzando todo el tráfico a pasar por su reverse proxy donde se realizan la terminación TLS, la limitación de velocidad y el control de acceso.

# docker-compose.yml - binding to localhost only
services:
  app:
    image: myapp:latest
    ports:
      - "127.0.0.1:3000:3000"  # Only reachable from localhost

Verifique el enlace:

ss -tlnp | grep 3000
# LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:* users:(("docker-proxy",...))

Fíjese bien: la salida muestra 127.0.0.1:3000, no 0.0.0.0:3000. el tráfico externo en la IP pública de su VPS no puede alcanzar el puerto 3000 directamente.

Sin el prefijo 127.0.0.1, Docker publica en todas las interfaces por defecto. En un VPS con IP pública, su servicio queda expuesto a internet, evitando su reverse proxy y potencialmente su firewall.

Referencia rápida de publicación de puertos

Sintaxis Se vincula a Accesible desde
-p 8080:80 0.0.0.0:8080 + [::]:8080 Todas partes (IPv4 + IPv6)
-p 127.0.0.1:8080:80 127.0.0.1:8080 Solo localhost
-p 0.0.0.0:8080:80 0.0.0.0:8080 Todas las interfaces IPv4
-p [::1]:8080:80 [::1]:8080 Solo localhost IPv6

¿Cómo aislar redes de contenedores en un solo VPS?

Cree redes bridge separadas para cada pila de aplicación. Los contenedores en redes diferentes no pueden comunicarse a menos que los conecte explícitamente a una red compartida. Para bases de datos y servicios internos, use el flag internal: true para bloquear todo acceso a internet saliente.

Arquitectura multi-red

Una configuración de producción típica en un VPS se ve así:

                 Internet
                    |
              [Reverse Proxy]
               /          \
         [frontend]    [api-net]
            |             |
          webapp        api-server
                          |
                     [db-net: internal]
                          |
                       postgres

El reverse proxy está conectado a frontend y api-net. El servidor API está conectado a api-net y db-net. PostgreSQL solo está en db-net, sin ruta a internet.

Aquí el archivo Docker Compose para esta configuración:

services:
  proxy:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend
      - api-net

  webapp:
    image: mywebapp:latest
    networks:
      - frontend

  api:
    image: myapi:latest
    environment:
      - DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
    networks:
      - api-net
      - db-net

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - db-net

networks:
  frontend:
    driver: bridge
  api-net:
    driver: bridge
  db-net:
    driver: bridge
    internal: true  # No internet access for the database network

volumes:
  pgdata:

El internal: true en db-net significa que PostgreSQL no puede alcanzar internet. No puede descargar actualizaciones, contactar servidores externos ni ser explotado como pivote para conexiones salientes. El servidor API puede alcanzar la base de datos porque está conectado tanto a api-net como a db-net.

Verificar el aislamiento de red

Después de iniciar la pila, confirme el aislamiento:

# List networks created by Compose
docker network ls --filter "name=myproject"

# Inspect a network to see which containers are connected
docker network inspect myproject_db-net --format '{{range .Containers}}{{.Name}} {{end}}'
# myproject-api-1 myproject-db-1

# Confirm the database cannot reach the internet
docker exec myproject-db-1 ping -c1 -W2 8.8.8.8
# ping: sendto: Network unreachable

Fíjese bien: "Network unreachable" confirma que el flag internal: true funciona. El contenedor de base de datos no tiene gateway por defecto.

¿Cómo habilitar IPv6 para contenedores Docker?

Habilite IPv6 globalmente en /etc/docker/daemon.json, luego habilítelo por red con una subred. Docker soporta dual-stack (IPv4 + IPv6) de forma nativa en Linux. El parámetro ip6tables, habilitado por defecto, gestiona las reglas de firewall IPv6 para contenedores.

Paso 1: configurar el daemon

Edite /etc/docker/daemon.json:

{
  "ipv6": true,
  "fixed-cidr-v6": "fd00:dead:beef::/64",
  "ip6tables": true,
  "default-address-pools": [
    { "base": "172.17.0.0/16", "size": 24 },
    { "base": "fd00:dead:beef::/48", "size": 64 }
  ]
}

fixed-cidr-v6 asigna una subred IPv6 /64 a la bridge por defecto. El bloque default-address-pools indica a Docker cómo asignar subredes para nuevas redes: bloques /24 del rango IPv4 y bloques /64 del rango IPv6.

Use un prefijo ULA (fd00::/8) para tráfico privado entre contenedores. Si su VPS tiene un rango IPv6 público asignado por su proveedor, puede usar una subred de ese rango para contenedores que necesiten direcciones IPv6 públicas.

Reinicie Docker:

sudo systemctl restart docker

Verifique que IPv6 está habilitado:

docker network inspect bridge --format '{{.EnableIPv6}}'
# true

Paso 2: crear una red dual-stack

docker network create --ipv6 --subnet 172.20.0.0/24 --subnet fd00:dead:beef:1::/64 app-v6

O en Docker Compose:

networks:
  app-v6:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.20.0.0/24
        - subnet: fd00:dead:beef:1::/64

Paso 3: verificar la conectividad dual-stack

docker run --rm --network app-v6 alpine ip -6 addr show eth0
# inet6 fd00:dead:beef:1::2/64 scope global

IPv6 solo se soporta en daemons Docker que corran en Linux. En sistemas antiguos, puede que necesite cargar el módulo del kernel ip6_tables antes de crear redes IPv6:

sudo modprobe ip6_tables

¿Cómo definir redes en Docker Compose?

Docker Compose crea una red por defecto para cada proyecto automáticamente. Cada servicio en el archivo Compose se une a esta red y puede resolver otros servicios por su nombre de servicio. Para la mayoría de aplicaciones de pila única, este comportamiento por defecto es suficiente.

Cuando necesite varias redes para aislamiento, defínalas explícitamente en la sección networks:

services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
    networks:
      - public

  app:
    image: node:20-alpine
    networks:
      - public
      - private

  redis:
    image: redis:7-alpine
    networks:
      - private

networks:
  public:
    driver: bridge
  private:
    driver: bridge
    internal: true

En este archivo, web puede alcanzar app a través de la red public. app puede alcanzar tanto web como redis. redis solo puede alcanzar app a través de private y no tiene acceso a internet.

Usar la red host en Compose

services:
  node-exporter:
    image: prom/node-exporter:latest
    network_mode: host
    pid: host
    restart: unless-stopped

network_mode: host reemplaza cualquier definición networks. No puede combinar el modo host con redes personalizadas en el mismo servicio.

Conectarse a redes externas

Si una red fue creada fuera de Compose (por otro stack o manualmente), referencíela como externa:

networks:
  shared-proxy:
    external: true

Esto permite que varios proyectos Compose compartan una red. Un patrón habitual: un stack Compose ejecuta el reverse proxy y crea la red. Otros stacks la declaran como externa y conectan sus servicios a ella.

Referencia rápida de comandos de red Docker

Comando Qué hace
docker network ls Listar todas las redes
docker network create mynet Crear una red bridge
docker network create --ipv6 --subnet fd00::/64 mynet Crear una red dual-stack
docker network inspect mynet Mostrar detalles de la red (subred, contenedores, opciones)
docker network connect mynet container1 Adjuntar un contenedor en ejecución a una red
docker network disconnect mynet container1 Desconectar un contenedor de una red
docker network prune Eliminar todas las redes no utilizadas
docker network rm mynet Eliminar una red específica

Límites de escalabilidad

Las redes bridge se vuelven inestables cuando más de 1.000 contenedores se conectan a una sola red. Es una limitación del kernel de Linux en el bridge device. Si ejecuta muchos contenedores, distribúyalos en varias redes por función en lugar de ponerlo todo en una sola bridge.

¿Algo salió mal?

Los contenedores no pueden resolverse por nombre. Probablemente está en la bridge por defecto. Cree una red personalizada y conecte ambos contenedores. Verifique con docker inspect <container> --format '{{json .NetworkSettings.Networks}}'.

El puerto está publicado pero no es accesible desde fuera. Compruebe si está vinculado a 127.0.0.1 en lugar de 0.0.0.0. Ejecute ss -tlnp | grep <port> en el host. Revise también sus reglas de firewall.

El contenedor no puede acceder a internet. La red puede tener internal: true. Verifique con docker network inspect <network> --format '{{.Internal}}'. Si devuelve true, la red bloquea el tráfico saliente por diseño.

IPv6 no funciona en los contenedores. Verifique que ip6tables está habilitado en daemon.json. En kernels antiguos, cargue el módulo: sudo modprobe ip6_tables. Compruebe que la red tiene IPv6 habilitado: docker network inspect <network> --format '{{.EnableIPv6}}'.

"Address already in use" con la red host. Otro proceso (o contenedor) ya ocupa ese puerto. Encuéntrelo con ss -tlnp | grep <port>. La red host no hace mapeo de puertos, así que los conflictos son directos.

Siguientes pasos

Su red de contenedores está configurada. Lo que sigue:

  • Añadir un reverse proxy para gestionar TLS y enrutamiento
  • Corregir el bypass del firewall de Docker para que los puertos publicados respeten sus reglas UFW/nftables
  • Añadir límites de recursos y healthchecks a sus servicios Compose
  • Volver a la vista general de Docker en VPS para la checklist de producción completa

Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.

¿Listo para probarlo?

Domina las redes Docker en un VPS dedicado.

Ver planes VPS
Docker bridge vs host en un VPS