Un contenedor tiene su propia red: su IP, sus puertos, su localhost. Por eso un nginx en marcha
dentro de un contenedor no se ve desde fuera hasta que publicas su puerto, y por eso dos
contenedores no se encuentran por su nombre si no están en la misma red propia. En este tema
aprendes las dos cosas, y a averiguar quién escucha en cada puerto.
Los ejercicios pasan en el servidor de pruebas de Cines Lumière, una cadena de cines: la web de la cartelera, la API de las sesiones y la base de datos de las reservas, cada una en su contenedor.
Publicar un puerto: -p
Un nginx arrancado sin más escucha en el puerto 80… de su contenedor. docker ps lo dice en
PORTS (80/tcp, sin flecha: no está publicado) y desde la máquina no llega nada:
tecnico@srv-pruebas:~$ curl -s -m 2 localhost:8080; echo "codigo $?" codigo 7
(El 7 de curl es “no he podido conectar”.) Con -p máquina:contenedor, Docker abre ese puerto en
la máquina y lo lleva al del contenedor. Primero va el de la máquina:
tecnico@srv-pruebas:~$ docker run -d --name taquilla -p 8080:80 nginx:1.27-alpine 07b361956bfb74a2ea66f68dfea81bf8ec8aebdace4265d4d718a5b375b45b57
tecnico@srv-pruebas:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 07b361956bfb nginx:1.27-alpine "/docker-entrypoint.…" 1 second ago Up Less than a second 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp taquilla
0.0.0.0:8080->80/tcp se lee “el 8080 de todas las direcciones de la máquina va al 80 del
contenedor” (y [::], lo mismo en IPv6). Ahora sí:
tecnico@srv-pruebas:~$ curl -s localhost:8080 | head -n 4 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title>
Con -p 80 (solo el del contenedor), Docker elige un puerto libre de la máquina (del 32768 en
adelante); con -P, publica así todos los puertos de la imagen.
Ejercicio
Que se vea desde fuera
La web de la cartelera tiene que verse desde fuera del servidor, en su puerto 8080. Arranca con nginx:1.27-alpine el contenedor cartelera publicando su puerto 80 en el 8080 de la máquina, y compruébalo desde la máquina.
Preparando la máquina…
¿Dónde escucha cada uno?
docker port enseña lo publicado de un contenedor:
tecnico@srv-pruebas:~$ docker port taquilla 80/tcp -> 0.0.0.0:8080 80/tcp -> [::]:8080
Y desde la máquina, ss enseña quién tiene el puerto: un docker-proxy por cada publicado (es
el programa de Docker que recoge lo que llega a la máquina y lo pasa al contenedor):
tecnico@srv-pruebas:~$ sudo ss -tlnp | grep -E "8080|State" State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=2804,fd=7)) LISTEN 0 4096 [::]:8080 [::]:* users:(("docker-proxy",pid=2809,fd=7))
Un puerto de la máquina solo puede tenerlo uno. Si otro contenedor ya lo tiene, Docker lo dice al arrancar (el contenedor se queda creado, sin arrancar):
tecnico@srv-pruebas:~$ docker run -d --name taquilla2 -p 8080:80 nginx:1.27-alpine 7ecf74f66d971dda6326922b5e7989d85f25dca0e36c14843f7a80ae6a7ba0da docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint taquilla2 (a5ff90c890c4ad4d89b3f1a90500678217ae76c1afbcb2036c529a786588e624): Bind for 0.0.0.0:8080 failed: port is already allocated Run 'docker run --help' for more information
Si el que lo tiene es un programa de la máquina (no un contenedor), el final dice otra cosa:
address already in use. En los dos casos, docker ps (o sudo ss -tlnp) te dice quién es. El
contenedor que no arrancó sigue ahí (Created): bórralo con docker rm antes de volver a crearlo
con el mismo nombre. O, si ya tiene el -p que tiene que tener, libera el puerto y arráncalo con
docker start: un contenedor guarda lo que se publica, y lo vuelve a pedir cada vez que arranca
(también uno parado al que otro le ha quitado el puerto mientras tanto; el error dice cuál es).
Ejercicio
El puerto está cogido
Al arrancar la cartelera en el 8080, Docker dice que el puerto ya está ocupado. Averigua qué contenedor lo tiene y arranca la cartelera en el 8081, sin tocar ese otro.
Preparando la máquina…
Solo para esta máquina
-p 5432:5432 publica en todas las direcciones de la máquina: también en la que da a
internet. Una base de datos que solo usan programas del propio servidor se publica solo en
127.0.0.1, poniendo la IP delante:
tecnico@srv-pruebas:~$ docker run -d --name db -e POSTGRES_PASSWORD=secreto -p 127.0.0.1:5432:5432 postgres 6f60e535f4f6967c1262a6cf0b2a71381b891e609de33bbe89edbe50b974e206
tecnico@srv-pruebas:~$ docker port db 5432/tcp -> 127.0.0.1:5432
Ejercicio
Solo para esta máquina
La base de datos de las reservas no puede verse desde fuera: solo la usan programas del propio servidor. Arranca postgres en el contenedor reservas-db, con la contraseña que quieras (POSTGRES_PASSWORD), publicando su 5432 solo en 127.0.0.1.
Preparando la máquina…
La red de serie: sin nombres
Cada contenedor arrancado sin más está en la red de serie, bridge (en la máquina, la interfaz
docker0, 172.17.0.1). Ahí los contenedores se ven por su IP, pero no por su nombre:
tecnico@srv-pruebas:~$ docker run --rm alpine ping -c 1 -W 1 web ping: bad address 'web'
Por su IP sí llega, pero la IP cambia cada vez que un contenedor arranca: no sirve para que una API encuentre su base de datos.
Redes propias: docker network
Una red creada por ti tiene su DNS: dentro de ella, cada contenedor se encuentra por su nombre.
tecnico@srv-pruebas:~$ docker network create cines c504e593c610d013797d2ab0eb24bd15aeb7c2e4d3919f4f7a7e4d67bba4d158
tecnico@srv-pruebas:~$ docker network ls NETWORK ID NAME DRIVER SCOPE 39cc508809d4 bridge bridge local c504e593c610 cines bridge local 8760ef4471c7 host host local c942a5c9eff2 none null local
Con --network el contenedor nace en ella; desde otro de la misma red, se le llama por su nombre:
tecnico@srv-pruebas:~$ docker run -d --name api --network cines nginx:1.27-alpine
tecnico@srv-pruebas:~$ docker run --rm --network cines alpine ping -c 1 -W 1 api PING api (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.089 ms --- api ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.089/0.089/0.089 ms
Quien contesta el nombre es el DNS de Docker: dentro de una red propia, el /etc/resolv.conf del
contenedor dice nameserver 127.0.0.11.
Ejercicio
Por su nombre
En la red de serie, los contenedores no se encuentran por su nombre. Crea la red cines, arranca en ella el contenedor api (con nginx:1.27-alpine) y, desde un alpine en la misma red, pide http://api.
Preparando la máquina…
Conectar y desconectar
Un contenedor puede estar en varias redes. docker network connect mete en otra a uno que ya está
en marcha (sin pararlo), y docker network inspect dice quién hay en cada una (en Containers):
tecnico@srv-pruebas:~$ docker network connect cines web
tecnico@srv-pruebas:~$ docker ps --filter network=cines CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e942f04a00df nginx:1.27-alpine "/docker-entrypoint.…" 7 seconds ago Up 7 seconds 80/tcp api 879a961dea5e nginx:1.27-alpine "/docker-entrypoint.…" 14 seconds ago Up 14 seconds 80/tcp web
docker network disconnect cines web lo saca.
Ejercicio
Uno que ya estaba en marcha
La web lleva días en marcha en la red de serie y ahora tiene que hablar con la api, que está en la red cines. Conéctala a esa red sin pararla, y comprueba que la red ya las tiene a las dos.
Preparando la máquina…
Borrar redes
Una red con contenedores en marcha no se puede borrar:
tecnico@srv-pruebas:~$ docker network rm cines Error response from daemon: error while removing network: network cines id c504e593c610d013797d2ab0eb24bd15aeb7c2e4d3919f4f7a7e4d67bba4d158 has active endpoints exit status 1
Los “endpoints activos” son los contenedores que siguen en ella. Desconéctalos, páralos o
bórralos, y entonces docker network rm cines funciona. docker network prune borra de una vez
las propias que no usa nadie (las de serie, bridge, host y none, no se borran nunca).
Ejercicio
La red que sobra
La red pruebas ya no hace falta, pero docker network rm dice que tiene endpoints activos. Bórrala; el contenedor prueba-taquilla que sigue en ella también sobra.
Preparando la máquina…
host y none
Además de bridge, hay dos redes de serie más:
--network host: el contenedor usa la red de la máquina tal cual. Su nginx escucha en el 80 de la máquina, sin-p(Docker ignora lo publicado) y sin red propia.--network none: sin red. Sololo.
Resumen
| Orden | Para qué |
|---|---|
docker run -p 8080:80 … |
publicar el 80 del contenedor en el 8080 de la máquina |
docker run -p 127.0.0.1:5432:5432 … |
publicarlo solo para la propia máquina |
docker port c · sudo ss -tlnp |
dónde escucha cada uno |
docker network create red |
una red propia (con nombres) |
docker run --network red … |
arrancar en ella |
docker network connect red c / disconnect |
meter o sacar uno que ya existe |
docker network inspect red |
quién hay en ella |
docker network rm red · prune |
borrarla (sin contenedores en marcha) |