Cristhian Villegas
DevOps11 min read0 views

Curso Docker #5: Redes en Docker — Comunicacion entre Contenedores

Curso Docker #5: Redes en Docker — Comunicacion entre Contenedores

Bienvenido al Curso de Docker - Parte 5 de 10

Logo de Docker

Fuente: Wikimedia Commons

¡Bienvenido a la Parte 5 del Curso de Docker! En los articulos anteriores aprendiste a crear contenedores, construir imagenes y persistir datos. Ahora vamos a resolver otra pieza fundamental: como se comunican los contenedores entre si.

En aplicaciones reales, casi siempre tienes multiples contenedores que necesitan hablar entre ellos: tu aplicacion web necesita conectarse a la base de datos, tu API necesita comunicarse con un cache Redis, tu frontend necesita llamar al backend. Las redes Docker hacen todo esto posible.

Nota: Este articulo asume que ya dominas los conceptos de los articulos 1-4 (contenedores, imagenes, Dockerfile, volumenes). Si no los has leido, te recomiendo hacerlo primero.

Panorama general de redes en Docker

Cuando Docker se instala, crea automaticamente tres redes por defecto:

bash
1# Ver las redes existentes
2docker network ls
3
4# Salida tipica:
5# NETWORK ID     NAME      DRIVER    SCOPE
6# a1b2c3d4e5f6   bridge    bridge    local
7# f6e5d4c3b2a1   host      host      local
8# 1a2b3c4d5e6f   none      null      local

Cada red tiene un driver que determina su comportamiento:

  • bridge: Red privada interna. Los contenedores pueden comunicarse entre si. Es la red por defecto
  • host: El contenedor comparte la red del host directamente. No hay aislamiento de red
  • none: Sin red. El contenedor esta completamente aislado
  • overlay: Para comunicar contenedores en diferentes maquinas (Docker Swarm)

La red bridge por defecto

Cuando ejecutas docker run sin especificar una red, el contenedor se conecta a la red bridge por defecto. Veamos como funciona:

bash
1# Crear dos contenedores en la red bridge por defecto
2docker run -d --name contenedor-1 alpine sleep 3600
3docker run -d --name contenedor-2 alpine sleep 3600
4
5# Ver las IPs asignadas
6docker inspect --format='{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' contenedor-1
7docker inspect --format='{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' contenedor-2
8# /contenedor-1 - 172.17.0.2
9# /contenedor-2 - 172.17.0.3
10
11# Instalar ping en contenedor-1 y probar conectividad
12docker exec contenedor-1 sh -c "apk add --no-cache iputils && ping -c 3 172.17.0.3"
13# PING 172.17.0.3: 64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.1ms
14
15# PERO: la comunicacion por NOMBRE no funciona en la red bridge por defecto
16docker exec contenedor-1 sh -c "ping -c 3 contenedor-2"
17# ping: bad address 'contenedor-2' -- NO RESUELVE
18
19# Limpiar
20docker rm -f contenedor-1 contenedor-2
Importante: La red bridge por defecto no tiene resolucion DNS por nombre. Solo puedes comunicarte por IP, lo cual es fragil porque las IPs pueden cambiar. Por eso Docker recomienda siempre crear redes personalizadas.

Redes personalizadas (user-defined bridge)

Las redes personalizadas son la forma correcta de conectar contenedores. Tienen ventajas clave sobre la red bridge por defecto:

  • DNS automatico: Los contenedores se encuentran por nombre
  • Aislamiento: Solo los contenedores en la misma red pueden comunicarse
  • Conexion/desconexion en caliente: Puedes agregar o quitar contenedores de una red sin reiniciarlos

Crear y gestionar redes

bash
1# Crear una red personalizada
2docker network create mi-red
3
4# Listar redes
5docker network ls
6
7# Inspeccionar una red (ver detalles)
8docker network inspect mi-red
9
10# Eliminar una red
11docker network rm mi-red
12
13# Eliminar todas las redes no utilizadas
14docker network prune

DNS automatico por nombre de contenedor

bash
1# Crear una red personalizada
2docker network create app-network
3
4# Crear dos contenedores en esa red
5docker run -d --name servidor --network app-network alpine sleep 3600
6docker run -d --name cliente --network app-network alpine sleep 3600
7
8# Ahora la comunicacion por NOMBRE funciona!
9docker exec cliente sh -c "apk add --no-cache iputils && ping -c 3 servidor"
10# PING servidor (172.18.0.2): 56 data bytes
11# 64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.1ms
12
13# Docker resuelve "servidor" automaticamente a su IP
14docker exec cliente sh -c "nslookup servidor"
15# Name:      servidor
16# Address 1: 172.18.0.2 servidor.app-network
17
18# Limpiar
19docker rm -f servidor cliente
20docker network rm app-network
Tip: El DNS automatico es la razon principal para usar redes personalizadas. En tu codigo, nunca escribas IPs — usa el nombre del contenedor como hostname. Por ejemplo, tu aplicacion se conecta a mysql://mi-mysql:3306 en lugar de mysql://172.18.0.5:3306.

Red host y red none

Red host

Con la red host, el contenedor usa directamente la red del host. No hay aislamiento ni port mapping — el contenedor escucha directamente en los puertos del host:

bash
1# Ejecutar Nginx directamente en el puerto 80 del host
2docker run -d --name nginx-host --network host nginx
3
4# No necesitas -p 80:80 porque el contenedor USA la red del host
5# Accede directamente a http://localhost:80
6
7# Ver que no hay aislamiento de red
8docker exec nginx-host sh -c "hostname -i"
9# Muestra la IP real de tu maquina, no una IP de Docker
10
11# Limpiar
12docker rm -f nginx-host
Importante: La red host solo funciona en Linux. En Docker Desktop (Windows/macOS), la red host se comporta de forma diferente debido a la VM subyacente. Usala con precaucion.

Red none

Con la red none, el contenedor no tiene acceso a ninguna red:

bash
1# Contenedor sin red
2docker run -d --name aislado --network none alpine sleep 3600
3
4# No puede comunicarse con nada
5docker exec aislado sh -c "ping -c 1 google.com"
6# ping: bad address 'google.com'
7
8docker rm -f aislado

Port mapping: exponer servicios al exterior

Los contenedores por defecto solo son accesibles desde otros contenedores en la misma red. Para que sean accesibles desde tu maquina o desde internet, necesitas mapear puertos con la bandera -p:

bash
1# Sintaxis: -p <puerto_host>:<puerto_contenedor>
2docker run -d -p 8080:80 --name web nginx
3
4# Explicacion:
5# Puerto 8080 en TU maquina -> Puerto 80 dentro del contenedor
6
7# Mapear a un puerto aleatorio del host
8docker run -d -p 80 --name web2 nginx
9# Docker asigna un puerto aleatorio, verlo con:
10docker port web2
11# 80/tcp -> 0.0.0.0:55001
12
13# Mapear multiples puertos
14docker run -d -p 8080:80 -p 8443:443 --name web3 nginx
15
16# Restringir a una interfaz especifica (solo localhost)
17docker run -d -p 127.0.0.1:8080:80 --name web-local nginx
18
19# Mapear UDP
20docker run -d -p 5353:53/udp --name dns-server mi-dns
21
22# Ver todos los mapeos de puertos
23docker port web
24
25# Limpiar
26docker rm -f web web2 web3 web-local
Tip: El port mapping con -p es la unica forma de acceder a un contenedor desde fuera de Docker (tu navegador, Postman, etc.). La comunicacion entre contenedores en la misma red no necesita -p — usan el DNS interno y los puertos originales del servicio.

Conectar y desconectar contenedores de redes

Puedes agregar o quitar un contenedor de una red sin necesidad de reiniciarlo:

bash
1# Crear dos redes
2docker network create red-frontend
3docker network create red-backend
4
5# Crear un contenedor en red-frontend
6docker run -d --name api --network red-frontend alpine sleep 3600
7
8# Conectar el mismo contenedor a red-backend (ahora esta en ambas)
9docker network connect red-backend api
10
11# Verificar que esta en ambas redes
12docker inspect --format='{{json .NetworkSettings.Networks}}' api | python3 -m json.tool
13# Muestra "red-frontend" y "red-backend"
14
15# Desconectar de una red
16docker network disconnect red-frontend api
17
18# Ahora solo esta en red-backend
19docker inspect --format='{{range $key, $val := .NetworkSettings.Networks}}{{$key}} {{end}}' api
20# red-backend
21
22# Limpiar
23docker rm -f api
24docker network rm red-frontend red-backend

Esta capacidad es util cuando un contenedor necesita comunicarse con multiples servicios en redes diferentes, como un API gateway que habla con el frontend y el backend.

Ejemplo practico: Node.js + MySQL en red personalizada

Vamos a construir un ejemplo realista: una aplicacion Node.js que se conecta a una base de datos MySQL, ambos en contenedores Docker comunicandose a traves de una red personalizada.

Paso 1: Crear la red

bash
1# Crear la red para nuestra aplicacion
2docker network create app-fullstack

Paso 2: Levantar MySQL

bash
1# Ejecutar MySQL con volumen persistente
2docker run -d --name mi-mysql \
3  --network app-fullstack \
4  -e MYSQL_ROOT_PASSWORD=root123 \
5  -e MYSQL_DATABASE=mi_app \
6  -e MYSQL_USER=app_user \
7  -e MYSQL_PASSWORD=app_pass \
8  -v mysql-datos:/var/lib/mysql \
9  mysql:8.0
10
11# Esperar a que MySQL este listo (puede tardar 30-60 segundos)
12docker logs -f mi-mysql
13# Busca: "ready for connections"
14# Ctrl+C para salir de los logs
15
16# Crear una tabla de prueba
17docker exec -it mi-mysql mysql -uapp_user -papp_pass mi_app -e "
18CREATE TABLE productos (
19  id INT AUTO_INCREMENT PRIMARY KEY,
20  nombre VARCHAR(100) NOT NULL,
21  precio DECIMAL(10,2) NOT NULL
22);
23INSERT INTO productos (nombre, precio) VALUES
24  ('Laptop', 15999.99),
25  ('Mouse', 299.50),
26  ('Teclado', 899.00);
27"

Paso 3: Crear la aplicacion Node.js

javascript
1// app.js
2const http = require('http');
3const mysql = require('mysql2/promise');
4
5const PORT = 3000;
6
7// Nota: el host es "mi-mysql" — el NOMBRE del contenedor
8// Docker resuelve esto automaticamente via DNS
9const dbConfig = {
10  host: 'mi-mysql',
11  port: 3306,
12  user: 'app_user',
13  password: 'app_pass',
14  database: 'mi_app'
15};
16
17const server = http.createServer(async (req, res) => {
18  if (req.url === '/productos') {
19    try {
20      const conn = await mysql.createConnection(dbConfig);
21      const [rows] = await conn.execute('SELECT * FROM productos');
22      await conn.end();
23
24      res.writeHead(200, { 'Content-Type': 'application/json' });
25      res.end(JSON.stringify({ productos: rows }));
26    } catch (err) {
27      res.writeHead(500, { 'Content-Type': 'application/json' });
28      res.end(JSON.stringify({ error: err.message }));
29    }
30  } else {
31    res.writeHead(200, { 'Content-Type': 'application/json' });
32    res.end(JSON.stringify({
33      message: 'API funcionando!',
34      endpoints: ['/productos']
35    }));
36  }
37});
38
39server.listen(PORT, () => {
40  console.log('API corriendo en puerto ' + PORT);
41});

Paso 4: El Dockerfile de la API

dockerfile
1FROM node:20-alpine
2WORKDIR /app
3COPY package.json .
4RUN npm install
5COPY app.js .
6EXPOSE 3000
7CMD ["node", "app.js"]
json
1{
2  "name": "api-docker-demo",
3  "version": "1.0.0",
4  "dependencies": {
5    "mysql2": "^3.6.0"
6  }
7}

Paso 5: Construir y ejecutar

bash
1# Construir la imagen de la API
2docker build -t mi-api:1.0 .
3
4# Ejecutar la API en la misma red que MySQL
5docker run -d --name mi-api \
6  --network app-fullstack \
7  -p 3000:3000 \
8  mi-api:1.0
9
10# Probar la API
11curl http://localhost:3000
12# {"message":"API funcionando!","endpoints":["/productos"]}
13
14curl http://localhost:3000/productos
15# {"productos":[{"id":1,"nombre":"Laptop","precio":"15999.99"},...]}
Tip: Observa que en el codigo de la API usamos host: 'mi-mysql' — el nombre del contenedor de MySQL. Docker resuelve ese nombre a la IP interna del contenedor automaticamente. Esto es posible gracias a la red personalizada app-fullstack que comparten ambos contenedores.

Paso 6: Verificar la red

bash
1# Inspeccionar la red para ver los contenedores conectados
2docker network inspect app-fullstack
3
4# Veras algo como:
5# "Containers": {
6#   "abc...": {
7#     "Name": "mi-mysql",
8#     "IPv4Address": "172.19.0.2/16"
9#   },
10#   "def...": {
11#     "Name": "mi-api",
12#     "IPv4Address": "172.19.0.3/16"
13#   }
14# }
15
16# Verificar conectividad desde la API al MySQL
17docker exec mi-api sh -c "ping -c 2 mi-mysql"
18# PING mi-mysql (172.19.0.2): 56 data bytes
19# 64 bytes from 172.19.0.2: icmp_seq=1 ttl=64 time=0.1ms

Paso 7: Limpiar

bash
1# Detener y eliminar contenedores
2docker rm -f mi-api mi-mysql
3
4# Eliminar la red
5docker network rm app-fullstack
6
7# Eliminar el volumen (si ya no lo necesitas)
8docker volume rm mysql-datos
9
10# Eliminar la imagen
11docker rmi mi-api:1.0

Diagrama de la arquitectura de red

Asi es como se ve la arquitectura de nuestro ejemplo:

bash
1# Arquitectura de red del ejemplo
2#
3#  Tu maquina (host)
4#  +-------------------------------------------------+
5#  |                                                   |
6#  |  Puerto 3000 ---+                                |
7#  |                  |                                |
8#  |  +-- app-fullstack (red Docker) ---------------+ |
9#  |  |               |                             | |
10#  |  |  +------------v-----------+                 | |
11#  |  |  |  mi-api (Node.js)     |                 | |
12#  |  |  |  Puerto 3000          |                 | |
13#  |  |  |  host: "mi-mysql"  ---+--->+            | |
14#  |  |  +------------------------+   |            | |
15#  |  |                               |            | |
16#  |  |  +----------------------------v---------+  | |
17#  |  |  |  mi-mysql (MySQL 8.0)               |  | |
18#  |  |  |  Puerto 3306                        |  | |
19#  |  |  |  Volumen: mysql-datos               |  | |
20#  |  |  +-------------------------------------+  | |
21#  |  |                                            | |
22#  |  +--------------------------------------------+ |
23#  +-------------------------------------------------+

Para mas detalles sobre las redes en Docker, consulta la documentacion oficial de networking.

Seguridad: Nunca expongas puertos de bases de datos (3306, 5432, 27017) al exterior en produccion usando -p. Las bases de datos deben ser accesibles solo desde otros contenedores en la misma red interna. Si necesitas acceder desde tu maquina para desarrollo, usa -p 127.0.0.1:3306:3306 para limitar el acceso a localhost.

Resumen y proximo articulo

En este quinto articulo del curso hemos aprendido:

  • Como funciona el networking en Docker: bridge, host, none, overlay
  • Por que la red bridge por defecto no tiene DNS y por que debemos crear redes personalizadas
  • Como crear y gestionar redes personalizadas con docker network
  • El DNS automatico que permite comunicarse por nombre de contenedor
  • Como funciona el port mapping con -p
  • Ejemplo completo: Node.js + MySQL conectados via red personalizada
  • Como conectar y desconectar contenedores de redes en caliente

En el proximo articulo (Parte 6 de 10) aprenderemos Docker Compose, la herramienta que simplifica todo lo que hicimos manualmente en este articulo. Con un solo archivo YAML y un comando, podras levantar toda tu arquitectura multi-contenedor.

¡Nos vemos en el siguiente articulo!

Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles