📚 **Esta entrada es parte de la serie _Guía de Docker_**, dividida en dos capítulos que se leen en orden:
> * 👉 **Parte 1: Contenedores, imágenes y aplicaciones**
* Parte 2: Docker Compose y temas avanzados
Contenedores
Hello world
Ejecutar el primer contenedor tipo Hello world con el comando docker run hello-world
InputPython!docker run hello-worldCopied
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world85e32844: Pull complete 457kB/2.457kBBDigest: sha256:dcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1cStatus: Downloaded newer image for hello-world:latestHello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/
Como no tenemos el contenedor guardado en local, Docker lo descarga de Docker Hub. Si ahora volvemos a ejecutar el contenedor, ya no aparecerá el primer mensaje, en el que se indica que se está descargando
InputPython!docker run hello-worldCopied
Hello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/
Para ver los contenedores que están corriendo, ejecutar docker ps
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Como vemos no hay ningún contenedor abierto. Pero, sin embargo, si ejecutamos docker ps -a (all) vemos que sí aparecen
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1efb51bbbf38 hello-world "/hello" 10 seconds ago Exited (0) 9 seconds ago strange_thompson5f5705e7603e hello-world "/hello" 15 seconds ago Exited (0) 14 seconds ago laughing_jang
Vemos que aparecen dos contenedores llamados hello-world que son los dos que hemos ejecutado antes. Por tanto, cada vez que ejecutamos el comando run, docker crea un nuevo contenedor, no ejecuta uno que ya exista
Si queremos tener más información de uno de los dos contenedores podemos ejecutar docker inspect <id>, donde <id> corresponde a la ID del contenedor que se ha mostrado en la lista anterior
InputPython!docker inspect 1efb51bbbf38Copied
[{"Id": "1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e","Created": "2023-09-04T03:59:17.795499354Z","Path": "/hello","Args": [],"State": {"Status": "exited","Running": false,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 0,"ExitCode": 0,"Error": "","StartedAt": "2023-09-04T03:59:18.406663026Z","FinishedAt": "2023-09-04T03:59:18.406181184Z"},"Image": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d","ResolvConfPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/resolv.conf","HostnamePath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hostname","HostsPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hosts","LogPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e-json.log","Name": "/strange_thompson",...}}}]
Como acordarnos de IDs es complicado para nosotros, Docker asigna nombres a los contenedores para facilitarnos la vida. Así, en la lista anterior, la última columna corresponde al nombre que ha asignado Docker a cada contenedor, de modo que si ahora ejecutamos docker inspect <name> obtendremos la misma información que con la ID
Vuelvo a ejecutar docker ps -a para volver a ver la lista
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1efb51bbbf38 hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago strange_thompson5f5705e7603e hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago laughing_jang
Y ahora ejecuto docker inspect <name> para ver la información del contenedor
InputPython!docker inspect strange_thompsonCopied
[{"Id": "1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e","Created": "2023-09-04T03:59:17.795499354Z","Path": "/hello","Args": [],"State": {"Status": "exited","Running": false,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 0,"ExitCode": 0,"Error": "","StartedAt": "2023-09-04T03:59:18.406663026Z","FinishedAt": "2023-09-04T03:59:18.406181184Z"},"Image": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d","ResolvConfPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/resolv.conf","HostnamePath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hostname","HostsPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/hosts","LogPath": "/var/lib/docker/containers/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e/1efb51bbbf38917affd1b5871db8e658ebfe0b2efa5ead17545680b7866f682e-json.log","Name": "/strange_thompson",...}}}]
Pero por qué con docker ps no vemos ningún contenedor y con docker ps -a sí. Esto es porque docker ps solo muestra los contenedores que están corriendo, mientras que docker ps -a muestra todos los contenedores, los que están corriendo y los que están apagados
Podemos crear un contenedor asignándole un nombre nosotros mediante el comando docker run --name <name> hello-world
InputPython!docker run --name hello_world hello-worldCopied
Hello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/
Esto será más cómodo para nosotros, ya que podremos controlar nosotros los nombres de los contenedores
Si ahora queremos crear otro contenedor con el mismo nombre no podremos, porque Docker no permite que se dupliquen los nombres de los contenedores. De modo que si queremos renombrar el contenedor podemos usar el comando docker rename <old name> <new name>
InputPython!docker rename hello_world hello_world2Copied
Tenemos ahora un montón de contenedores iguales. Así que si queremos borrar alguno tenemos que usar el comando docker rm <id> o docker rm <name>
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESf432c9c2ca21 hello-world "/hello" 9 seconds ago Exited (0) 8 seconds ago hello_world21efb51bbbf38 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago strange_thompson5f5705e7603e hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago laughing_jang
InputPython!docker rm hello_world2Copied
hello_world2
Si volvemos a ver la lista de contenedores, el contenedor hello_world2 ya no estará
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1efb51bbbf38 hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago strange_thompson5f5705e7603e hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago laughing_jang
Si queremos borrar todos los contenedores, podemos hacerlo uno a uno, pero como es muy pesado, podemos borrar todos mediante el comando docker container prune. Este comando elimina solo los contenedores que estén parados
InputPython!docker container pruneCopied
WARNING! This will remove all stopped containers.Are you sure you want to continue? [y/N] y
Docker pregunta si estás seguro, y si le dices que sí, borra todos. Si ahora listas todos los contenedores, no aparece ninguno
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
El modo interactivo
Vamos a ejecutar un Ubuntu mediante el comando docker run ubuntu
InputPython!docker run ubuntuCopied
Unable to find image 'ubuntu:latest' locallylatest: Pulling from library/ubuntuDigest: sha256:20fa2d7bb4de7723f542be5923b06c4d704370f0390e4ae9e1c833c8785644c1[1AStatus: Downloaded newer image for ubuntu:latest
Como vemos, ahora ha tardado más en descargar. Si listamos los contenedores mediante el comando docker ps, vemos que no aparece el contenedor que acabamos de crear, es decir, no está corriendo.
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Listamos ahora todos los contenedores
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESda16b3a85178 ubuntu "bash" 4 seconds ago Exited (0) 3 seconds ago hardcore_kare
Vemos que el estado del contenedor es Exited (0)
Si nos fijamos en el comando del contenedor aparece bash y junto al estado Exited (0) nos indica que ha arrancado Ubuntu, ha ejecutado su *bash*, ha terminado la ejecución y ha devuelto un 0. Esto pasa porque al Bash de Ubuntu no se le ha dicho nada que hacer. Para solucionarlo, ahora vamos a ejecutar el contenedor mediante el comando docker run -it ubuntu, con it lo que le estamos indicando es que lo queremos ejecutar en modo interactivo
InputPython!docker run -it ubuntuCopied
root@5b633e9d838f:/#
Ahora vemos que estamos dentro del bash de Ubuntu. Si ejecutamos el comando cat /etc/lsb-release podemos ver la distribución de Ubuntu
InputPython!root@5b633e9d838f:/# cat /etc/lsb-releaseCopied
DISTRIB_ID=UbuntuDISTRIB_RELEASE=22.04DISTRIB_CODENAME=jammyDISTRIB_DESCRIPTION="Ubuntu 22.04.1 LTS"
Si abrimos otra terminal y vemos la lista de contenedores, ahora sí aparecerá el contenedor corriendo Ubuntu
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5b633e9d838f ubuntu "bash" 3 minutes ago Up 3 minutes funny_mirzakhani
Vemos el contenedor con Ubuntu y en su estado podemos ver UP
Si vemos ahora la lista de todos los contenedores, veremos que aparecen los dos contenedores con Ubuntu, el primero apagado y el segundo el que está corriendo
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5b633e9d838f ubuntu "bash" 3 minutes ago Up 3 minutes funny_mirzakhanida16b3a85178 ubuntu "bash" 3 minutes ago Exited (0) 3 minutes ago hardcore_kare
Si volvemos a la terminal donde teníamos Ubuntu corriendo dentro de un Docker, si escribimos exit saldremos de Ubuntu.
InputPython!root@5b633e9d838f:/# exitCopied
exit
Si ejecutamos docker ps, el contenedor ya no aparece
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Pero si ejecuto docker ps -a sí que aparece. Esto quiere decir que el contenedor se apagó
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5b633e9d838f ubuntu "bash" 4 minutes ago Exited (0) 27 seconds ago funny_mirzakhanida16b3a85178 ubuntu "bash" 4 minutes ago Exited (0) 4 minutes ago hardcore_kare
Esto ocurre porque al escribir exit, en realidad lo estamos escribiendo en la consola del bash de Ubuntu, lo que significa que estamos terminando el proceso bash de Ubuntu.
Ciclo de vida de un contenedor
En Docker, cuando el proceso principal de un contenedor se termina, se apaga el contenedor. Dentro de un contenedor pueden ejecutarse varios procesos, pero solo cuando se termina el proceso principal se apaga el contenedor
Por tanto, si queremos correr un contenedor que no se apague cuando finalice un proceso, debemos hacer que su proceso principal no termine. En este caso, que no finalice bash
Si queremos ejecutar un contenedor con Ubuntu, pero que no finalice cuando termine el proceso de Bash lo podemos hacer de la siguiente manera
InputPython!docker run --name alwaysup -d ubuntu tail -f /dev/nullCopied
ce4d60427dcd4b326d15aa832b816c209761d6b4e067a016bb75bf9366c37054
Lo que hacemos es primero darle el nombre alwaysup, en segundo lugar pasarle la opción -d (detach) para que el contenedor se ejecute en segundo plano y por último le decimos el proceso principal que queremos que se ejecute en el contenedor, que en este caso es tail -f /dev/null que equivale a un comando nop
Esto nos devolverá la ID del contenedor, pero no estaremos dentro de Ubuntu como pasaba antes
Si ahora vemos la lista de contenedores que se están ejecutando, aparece el contenedor que acabamos de crear
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 18 seconds ago Up 17 seconds alwaysup
Como ya tenemos un contenedor corriendo siempre, podemos conectarnos al él mediante el comando exec. Le decimos el nombre o la ID del contenedor y le pasamos el proceso que queremos que se ejecute. Además pasamos la opción -it para decirle que sea interactivo
InputPython!docker exec -it alwaysup bashCopied
root@ce4d60427dcd:/#
Ahora volvemos a estar dentro de Ubuntu. Si ejecutamos el comando ps -aux podemos ver una lista de los procesos que se están ejecutando dentro de Ubuntu.
InputPython!ps -auxCopied
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.0 2820 1048 ? Ss 13:04 0:00 tail -f /dev/nullroot 7 0.0 0.0 4628 3796 pts/0 Ss 13:04 0:00 bashroot 15 0.0 0.0 7060 1556 pts/0 R+ 13:05 0:00 ps -aux
Vemos solo tres procesos, el ps -aux, el bash y el tail -f /dev/null
Este contenedor va a estar siempre encendido mientras el proceso tail -f /dev/null siga corriendo
Si salimos del contenedor con el comando exit y ejecutamos el comando docker ps vemos que el contenedor sigue encendido
InputPython!exitCopied
exit
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 2 minutes ago Up 2 minutes alwaysup
Para poder finalizar el proceso y poder apagar el contenedor debemos usar el comando docker stop <name>
InputPython!docker stop alwaysupCopied
alwaysup
Si ahora volvemos a listar los contenedores encendidos, ya no aparece el contenedor con Ubuntu
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Y si listamos todos los contenedores, aparece el contenedor con Ubuntu, y su estado Exited
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 14 minutes ago Exited (137) About a minute ago alwaysup5b633e9d838f ubuntu "bash" 19 minutes ago Exited (0) 15 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 20 minutes ago Exited (0) 20 minutes ago hardcore_kare
También podemos pausar un contenedor mediante el comando docker pause <name>
InputPython!docker run --name alwaysup -d ubuntu tail -f /dev/nullCopied
8282eaf9dc3604fa94df206b2062287409cc92cbcd203f1a018742b5c171c9e4
Ahora lo pausamos
InputPython!docker pause alwaysupCopied
alwaysup
Si volvemos a ver todos los contenedores, vemos que el contenedor con Ubuntu está pausado
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8282eaf9dc36 ubuntu "tail -f /dev/null" 41 seconds ago Up 41 seconds (Paused) alwaysup5b633e9d838f ubuntu "bash" 19 minutes ago Exited (0) 15 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 20 minutes ago Exited (0) 20 minutes ago hardcore_kare
Contenedores de un solo uso
Si a la hora de ejecutar un contenedor, ponemos la opción --rm, ese contenedor se va a borrar cuando termine de ejecutarse.
InputPython!docker run --rm --name autoremove ubuntu:latestCopied
Si ahora vemos qué contenedores tenemos
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Vemos que no está el contenedor que acabamos de crear
Exponer contenedores al mundo exterior
Vamos a crear un nuevo contenedor con un servidor
InputPython!docker run -d --name proxy nginxCopied
Unable to find image 'nginx:latest' locallylatest: Pulling from library/nginxf1ad4ce1: Pulling fs layerb079d0f8: Pulling fs layer5fbbebc6: Pulling fs layerffdd25f4: Pulling fs layer32c8fba2: Pulling fs layer24b8ba39: Pull complete 393kB/1.393kBB[5ADigest: sha256:2888a97f7c7d498bbcc47ede1ad0f6ced07d72dfd181071dde051863f1f79d7bStatus: Downloaded newer image for nginx:latest1a530e04f14be082811b72ea8b6ea5a95dad3037301ee8a1351a0108ff8d3b30
Esto crea un servidor, vamos a volver a listar los contenedores que están corriendo
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1a530e04f14b nginx "/docker-entrypoint.…" 1 second ago Up Less than a second 80/tcp proxy
Ahora aparece una nueva columna con el puerto, y nos dice que el servidor que acabamos de crear está en el puerto 80 bajo el protocolo tcp.
Si abrimos un navegador e intentamos conectarnos al servidor mediante http://localhost:80 no conseguimos conectar. Esto es porque cada contenedor tiene su propia interfaz de red. Es decir, el servidor está escuchando en el puerto 80 del contenedor, pero nosotros estamos intentando conectar al puerto 80 del host
Paramos el contenedor para relanzarlo de otra forma
InputPython!docker stop proxyCopied
proxy
Si listamos los contenedores, no aparece corriendo
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Lo borramos para volver a crearlo
InputPython!docker rm proxyCopied
proxy
Si listamos todos los contenedores ya no están
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 19 minutes ago Exited (137) 5 minutes ago alwaysup5b633e9d838f ubuntu "bash" 24 minutes ago Exited (0) 20 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 24 minutes ago Exited (0) 24 minutes ago hardcore_kare
Para volver a crear el contenedor con el servidor y poderlo ver desde el host, tenemos que usar la opción -p (publish), indicando en primer lugar el puerto en el que queremos verlo en el host y a continuación el puerto del contenedor, es decir, -p <ip host>:<ip contenedor>
InputPython!docker run -d --name proxy -p 8080:80 nginxCopied
c199235e42f76a30266f6e1af972e0a59811806eb3d3a9afdd873f6fa1785eae
Listamos los contenedores
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESc199235e42f7 nginx "/docker-entrypoint.…" 22 seconds ago Up 21 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp proxy
Vemos que el puerto del contenedor es 0.0.0.0:8080->80/tcp. Si ahora vamos a un navegador e introducimos 0.0.0.0:8080 podremos acceder al servidor del contenedor
Al listar los contenedores, en la columna PORTS indica 0.0.0.0:8080->80/tcp, lo que nos ayuda a ver la relación de puertos
Para ver los logs del contenedor, mediante el comando docker logs <name> puedo ver los registros del contenedor
InputPython!docker logs proxyCopied
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d//docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh/docker-entrypoint.sh: Configuration complete; ready for start up2022/09/13 13:24:06 [notice] 1#1: using the "epoll" event method2022/09/13 13:24:06 [notice] 1#1: nginx/1.23.12022/09/13 13:24:06 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)2022/09/13 13:24:06 [notice] 1#1: OS: Linux 5.15.0-46-generic2022/09/13 13:24:06 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:10485762022/09/13 13:24:06 [notice] 1#1: start worker processes2022/09/13 13:24:06 [notice] 1#1: start worker process 312022/09/13 13:24:06 [notice] 1#1: start worker process 322022/09/13 13:24:06 [notice] 1#1: start worker process 332022/09/13 13:24:06 [notice] 1#1: start worker process 342022/09/13 13:24:06 [notice] 1#1: start worker process 352022/09/13 13:24:06 [notice] 1#1: start worker process 362022/09/13 13:24:06 [notice] 1#1: start worker process 372022/09/13 13:24:06 [notice] 1#1: start worker process 382022/09/13 13:24:06 [notice] 1#1: start worker process 392022/09/13 13:24:06 [notice] 1#1: start worker process 402022/09/13 13:24:06 [notice] 1#1: start worker process 41...172.17.0.1 - - [13/Sep/2022:13:24:40 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://0.0.0.0:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " üâV$Zqi'×ü[ïºåÇè÷&3nSëÉìÂØÑ¾ Ç?áúaÎuã/ØRfOHì+\»±¿Òm°9 úúÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " ü)bCÙmñëd"ÏÄE#~LÁµk«lî[0 ÐÒ` Æ Rêq{Pòûâ¨IôtH~Ê1-| êêÀ+À/À,À0̨̩ÀÀ / 5 " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:26:28 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"
Ahora puedo ver todas las peticiones que se le han hecho al servidor. Pero si quiero ver los logs en tiempo real, mediante docker logs -f <name> lo puedo hacer
InputPython!docker logs -f proxyCopied
Ahora puedo ver los logs en tiempo real. Para salir, introducir CTRL+C
Como puede llegar un momento en el que haya muchos logs, si solo quieres los últimos logs, mediante la opción --tail <num> puedes ver los últimos <num> logs. Si añado la opción -f estaremos viendo siempre los últimos <num> logs
InputPython!docker logs --tail 10 proxyCopied
2022/09/13 13:24:06 [notice] 1#1: start worker process 412022/09/13 13:24:06 [notice] 1#1: start worker process 42172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " üE¶EgóÉÊì§y#3ÜQïê$¿# ÷-,s!rê|®ß¡LZª4y³t«ÀÎ_¸çÿ'Ï êêÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " ü}©Dr{;z¼zÂxßæl?§àDoK'g»µ %»ýÙ?Û³TöcJ÷åÂÒ¼¢£ë½=R¼ ÊÊÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:24:39 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"2022/09/13 13:24:40 [error] 34#34: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "0.0.0.0:8080", referrer: "http://0.0.0.0:8080/"172.17.0.1 - - [13/Sep/2022:13:24:40 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://0.0.0.0:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " üâV$Zqi'×ü[ïºåÇè÷&3nSëÉìÂØÑ¾ Ç?áúaÎuã/ØRfOHì+\»±¿Òm°9 úúÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " ü)bCÙmñëd"ÏÄE#~LÁµk«lî[0 ÐÒ` Æ Rêq{Pòûâ¨IôtH~Ê1-| êêÀ+À/À,À0̨̩ÀÀ / 5 " 400 157 "-" "-" "-"172.17.0.1 - - [13/Sep/2022:13:26:28 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"
Si además añadimos la opción -t podemos ver la fecha y hora de cada log, de esta manera si hemos tenido un problema, podemos saber en qué momento ha ocurrido
InputPython!docker logs --tail -t 10 proxyCopied
2022-09-13T13:24:06.573362728Z 2022/09/13 13:24:06 [notice] 1#1: start worker process 412022-09-13T13:24:06.651127107Z 2022/09/13 13:24:06 [notice] 1#1: start worker process 422022-09-13T13:24:16.651160189Z 172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " üE¶EgóÉÊì§y#3ÜQïê$¿# ÷-,s!rê|®ß¡LZª4y³t«ÀÎ_¸çÿ'Ï êêÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"2022-09-13T13:24:16.116817914Z 172.17.0.1 - - [13/Sep/2022:13:24:16 +0000] " ü}©Dr{;z¼zÂxßæl?§àDoK'g»µ %»ýÙ?Û³TöcJ÷åÂÒ¼¢£ë½=R¼ ÊÊÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"2022-09-13T13:24:39.117398081Z 172.17.0.1 - - [13/Sep/2022:13:24:39 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"2022-09-13T13:24:39.117412408Z 2022/09/13 13:24:40 [error] 34#34: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "0.0.0.0:8080", referrer: "http://0.0.0.0:8080/"2022-09-13T13:24:40.117419389Z 172.17.0.1 - - [13/Sep/2022:13:24:40 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://0.0.0.0:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"2022-09-13T13:25:00.117434249Z 172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " üâV$Zqi'×ü[ïºåÇè÷&3nSëÉìÂØÑ¾ Ç?áúaÎuã/ØRfOHì+\»±¿Òm°9 úúÀ+À/À,À0̨̩ÀÀ / 5 localhost ÿ " 400 157 "-" "-" "-"2022-09-13T13:25:00.223560881Z 172.17.0.1 - - [13/Sep/2022:13:25:00 +0000] " ü)bCÙmñëd"ÏÄE#~LÁµk«lî[0 ÐÒ` Æ Rêq{Pòûâ¨IôtH~Ê1-| êêÀ+À/À,À0̨̩ÀÀ / 5 " 400 157 "-" "-" "-"2022-09-13T13:26:25.223596738Z 172.17.0.1 - - [13/Sep/2022:13:26:28 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" "-"
Paramos y borramos el contenedor
InputPython!docker rm -f proxyCopied
proxy
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 26 minutes ago Exited (137) 13 minutes ago alwaysup5b633e9d838f ubuntu "bash" 31 minutes ago Exited (0) 27 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 32 minutes ago Exited (0) 32 minutes ago hardcore_kare
Datos en Docker
Bind mounts
Vamos a ver los contenedores que tenemos parados
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 26 minutes ago Exited (137) 13 minutes ago alwaysup5b633e9d838f ubuntu "bash" 31 minutes ago Exited (0) 28 minutes ago funny_mirzakhanida16b3a85178 ubuntu "bash" 32 minutes ago Exited (0) 32 minutes ago hardcore_kare
Vamos a borrar los dos de Ubuntu en los que su comando principal es la Bash y vamos a dejar el que dejamos como no operación
InputPython!docker rm funny_mirzakhaniCopied
funny_mirzakhani
InputPython!docker rm hardcore_kareCopied
hardcore_kare
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESce4d60427dcd ubuntu "tail -f /dev/null" 27 minutes ago Exited (137) 14 minutes ago alwaysup
Vamos a volver a ejecutar el contenedor de Ubuntu que hemos dejado, esto se hace mediante el comando start
InputPython!docker start alwaysupCopied
alwaysup
Nos metemos otra vez dentro del
InputPython!docker exec -it alwaysup bashCopied
root@ce4d60427dcd:/#
En el contenedor, puedo crear una nueva carpeta que se llame dockerfolder
InputPython!mkdir dockerfolderCopied
Si listamos los archivos aparecerá la nueva carpeta
InputPython!lsCopied
bin boot dev dockerfolder etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
Si salimos del contenedor
InputPython!exitCopied
exit
Y lo borramos
InputPython!docker rm -f alwaysupCopied
alwaysup
Si listamos todos los contenedores, ya no aparece el último que hemos creado
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Vamos a volver a hacer todo, pero primero vamos a crear una carpeta en el host en la que compartiremos los datos con el contenedor
InputPython!mkdir dockerHostFolderCopied
Vemos que dentro de la carpeta no hay nada
InputPython!ls dockerHostFolderCopied
Ahora obtenemos nuestra ruta absoluta
InputPython!pwdCopied
/home/wallabot/Documentos/web/portafolio/posts
Volvemos a crear el contenedor pero añadiendo la opción -v (bind mount). A continuación se añade la ruta absoluta de la carpeta del host y la ruta absoluta de la carpeta en el contenedor, -v <host path>:<container path>
InputPython!docker run -d --name alwaysup -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/nullCopied
4ede4512c293bdcc155e9c8e874dfb4a28e5163f4d5c7ddda24ad2863f28921b
Entramos al contenedor, listamos los archivos y ya aparece la carpeta que habíamos creado
InputPython!docker exec -it alwaysup bashCopied
root@4ede4512c293:/#
InputPythonroot@4ede4512c293:/# lsCopied
bin dev etc lib lib64 media opt root sbin sys usrboot dockerContainerFolder home lib32 libx32 mnt proc run srv tmp var
Vamos al directorio del contenedor que hemos compartido, creamos un archivo y salimos del contenedor
InputPythonroot@4ede4512c293:/# cd dockerContainerFolderCopied
InputPythonroot@4ede4512c293:/dockerContainerFolder# touch bindFile.txtCopied
InputPythonroot@4ede4512c293:/dockerContainerFolder# exitCopied
exit
Vemos qué hay dentro de la carpeta compartida
InputPython!ls dockerHostFolderCopied
bindFile.txt
Pero es más, si borramos el contenedor, el archivo sigue allí
InputPython!docker rm -f alwaysupCopied
alwaysup
InputPython!ls dockerHostFolderCopied
bindFile.txt
Si vuelvo a crear un contenedor compartiendo las carpetas, todos los archivos estarán en el contenedor
InputPython!docker run -d --name alwaysup -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/nullCopied
6c021d37ea29d8b23fe5cd4968baa446085ae1756682f65340288b4c851c362d
InputPython!docker exec -it alwaysup bashCopied
root@6c021d37ea29:/#
InputPython!root@6c021d37ea29:/# ls dockerContainerFolder/Copied
bindFile.txt:/#
Eliminamos el contenedor
InputPython!docker rm -f alwaysupCopied
alwaysup
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Volúmenes
Los volúmenes se crearon como una evolución de los bind mounts para dar más seguridad. Podemos listar todos los volúmenes de Docker mediante docker volume ls
InputPython!docker volume lsCopied
DRIVER VOLUME NAME
Vamos a crear un nuevo volumen para el contenedor de ubuntu, para ello usamos el comando docker volume create <volume name>
InputPython!docker volume create ubuntuVolumeCopied
ubuntuVolume
Si volvemos a listar los volúmenes aparecerá el que acabamos de crear
InputPython!docker volume lsCopied
DRIVER VOLUME NAMElocal ubuntuVolume
Sin embargo, no aparece como una carpeta en el sistema de archivos del host. Con ls -d */ listamos todas las carpetas
InputPython!ls -d */Copied
dockerHostFolder/ __pycache__/
Vamos a volver a crear un contenedor, pero ahora lo creamos con el volumen que acabamos de crear con la opción --mount, indicando el volumen fuente mediante src=<volume name> (si el volumen no existiese, docker lo crearía), a continuación el destino separado por una ,, dst=<container path>, es decir --mount src=<volume name>,dst=<container path>
InputPython!docker run -d --name alwaysup --mount src=ubuntuVolume,dst=/dockerVolumeFolder ubuntu tail -f /dev/nullCopied
42cdcddf4e46dc298a87b0570115e0b2fc900cb4c6db5eea22a61409b8cb271d
Una vez creado podemos ver los volúmenes del contenedor mediante el comando inspect y filtrando por '{{.Mounts}}'
$ docker inspect --format '{{.Mounts}}' alwaysup
[ {
volume ubuntuVolume /var/lib/docker/volumes/ubuntuVolume/_data /dockerVolumeFolder local z true
}
]Vemos que el volumen se llama ubuntuVolume y además podemos ver la ruta donde está guardado, en este caso en /var/lib/docker/volumes/ubuntuVolume/_data. Hacemos lo mismo que antes, nos metemos en el contenedor, creamos un archivo en la ruta del volumen, salimos y vemos en el host si se ha creado
$ docker exec -it alwaysup bash
root@42cdcddf4e46:/# touch dockerVolumeFolder/volumeFile.txt
root@42cdcddf4e46:/# exit$ sudo ls /var/lib/docker/volumes/ubuntuVolume/_data
volumeFile.txtEstá el archivo creado
Insertar y extraer archivos de un contenedor
Primero vamos a crear un archivo que queremos copiar dentro de un contenedor
InputPython!touch dockerHostFolder/text.txtCopied
Entramos en el contenedor
$ docker exec -it alwaysup bash
root@42cdcddf4e46:/#Creamos una nueva carpeta donde vamos a copiar el archivo y salimos
root@42cdcddf4e46:/# mkdir folderToCopy
root@42cdcddf4e46:/# ls
bin boot dev dockerVolumeFolder etc folderToCopy home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@42cdcddf4e46:/# exit
exitCopiamos dentro del contenedor el archivo mediante el comando cp, indicando el archivo que **quiero** copiar, el contenedor donde lo queremos copiar y la ruta dentro del contenedor, docker cp <file> <container>:<container path>
InputPython!docker cp dockerHostFolder/text.txt alwaysup:/folderToCopyCopied
Volvemos a entrar al contenedor y comprobamos que esté el archivo
$ docker exec -it alwaysup bash
root@42cdcddf4e46:/# ls folderToCopy/
text.txtSalimos del contenedor
/# exit
exitAhora vamos a extraer el archivo del contenedor y lo vamos a guardar en el host con otro nombre, para ello usamos otra vez el comando cp, pero indicando ahora el contenedor, la ruta del archivo en el contenedor y la ruta y nombre que queremos que tenga el archivo en el host, docker cp <container>:<docker file path> <host file path>
InputPython!docker cp alwaysup:/folderToCopy/text.txt dockerHostFolder/fileExtract.txtCopied
Vemos que está en el host
InputPython!ls dockerHostFolderCopied
bindFile.txt fileExtract.txt text.txt
Aunque el contenedor esté parado también se pueden copiar archivos
Por último, borramos el contenedor
InputPython!docker rm -f alwaysupCopied
alwaysup
Imágenes
Conceptos fundamentales
Las imágenes son los archivos ("plantillas") con toda la configuración para crear un contenedor. Cada vez que creamos un contenedor se crea a partir de una imagen. Cuando creábamos contenedores nuevos, la primera vez salía un mensaje diciendo que no teníamos la imagen y que iba a descargarla. En Docker Hub existen multitud de imágenes con todo tipo de máquinas, pero para un entorno de desarrollo muy específico podemos crear nuestra propia plantilla para pasársela a alguien y trabaje en un contenedor con la misma configuración que el nuestro
Podemos ver todas las imágenes que tenemos guardadas en nuestro ordenador mediante el comando docker image ls
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Podemos ver los tamaños, y podemos ver cómo la de nginx ocupa mucho y por eso tardó más en descargarse que el resto
Otra columna que podemos ver es la de TAG, esto indica la versión de la imagen. En todas pone latest, esto quiere decir que es la última. Es decir, en el momento de descargárnosla nos hemos descargado la última versión que hay en Docker Hub. Esto en un entorno de desarrollo no es óptimo, porque nosotros podemos descargarnos una imagen de Ubuntu, y si no especificamos versión se baja la última, por ejemplo la 20.04. Pero después de un tiempo alguien puede querer desarrollar contigo y descargarse esa imagen, pero al no especificar la versión se descargará otra vez la última, que en su caso puede ser la 22.04. Esto puede dar lugar a problemas y a que cosas que a una de las personas le funcione y a la otra no
Podemos ver todas las imágenes que hay en docker hub yendo a https://hub.docker.com/. Ahí podrás buscar la imagen que mejor se adapte al proyecto que quieras hacer. Si navegamos a la imagen de Ubuntu, por ejemplo, podemos ver las versiones (tags) de las imágenes.
Vamos a descargar, **pero no ejecutar** una imagen. Para ello usamos el comando docker pull <hub> <image name>:<tag>. Si no indicamos el hub, lo descargará de Docker Hub por defecto, pero podemos indicar otro, por ejemplo uno privado de nuestra organización. También, si no indicamos el tag, por defecto bajará la última versión
InputPython!docker pull ubuntu:20.04Copied
20.04: Pulling from library/ubuntuDigest: sha256:35ab2bf57814e9ff49e365efd5a5935b6915eede5c7f8581e9e1b85e0eecbe16[1AStatus: Downloaded newer image for ubuntu:20.04docker.io/library/ubuntu:20.04
Si volvemos a listar las imágenes, vemos que ahora tenemos dos imágenes de Ubuntu, una con el tag 20.04 y otra con el tag latest
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Crear imágenes mediante Dockerfile
Creamos un directorio en el host llamado dockerImages para trabajar en él
InputPython!mkdir dockerImagesCopied
Creamos un archivo Dockerfile con el que crearemos una imagen
InputPython!touch dockerImages/DockerfileCopied
Abrimos el archivo creado con nuestro editor preferido y escribimos lo siguiente:
FROM ubuntu:latestEsto le dice a docker que cree la imagen a raíz de la imagen latest de Ubuntu
A continuación, escribimos un comando que se va a ejecutar en tiempo de compilación
RUN touch /test.txtEsto quiere decir que cuando se compile el Dockerfile se ejecutará ese comando, pero no cuando se corra el contenedor de la imagen
Al final el Dockerfile queda así:
FROM ubuntu:latest
RUN touch /test.txtCompilamos el Dockerfile mediante el comando build, con la opción -t podemos darle un tag. Por último, hay que indicarle la ruta del contexto de build, más adelante explicaremos esto
InputPython!docker build -t ubuntu:test ./dockerImagesCopied
Sending build context to Docker daemon 2.048kBStep 1/2 : FROM ubuntu:latest---> 2dc39ba059dcStep 2/2 : RUN touch /test.txt---> Using cache---> a78cf3ea16d8Successfully built a78cf3ea16d8Successfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Como vemos se compila en 2 pasos, cada uno tiene una id, cada una de esas ids son capas de la imagen, esto también lo veremos más adelante
Volvemos a ver las imágenes que tenemos guardadas en nuestro ordenador y aparece la que acabamos de crear
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu test a78cf3ea16d8 8 minutes ago 77.8MBnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Corremos el contenedor a partir de la imagen que acabamos de crear
$ docker run -it ubuntu:test
root@b57b9d4eedeb:/#Entramos en el bash del contenedor. Como dijimos, el comando RUN se ejecuta en tiempo de compilación de la imagen, por lo que el archivo que hemos pedido que se cree debería estar en nuestro contenedor
root@b57b9d4eedeb:/# ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys test.txt tmp usr varEs importante entender que ese archivo se creó cuando se construyó la imagen, es decir, la imagen del contenedor ya tiene ese archivo. No se crea cuando se lanza el contenedor
Salimos del contenedor
root@b57b9d4eedeb:/# exit
exitComo ya tenemos una imagen, la podríamos subir al Hub de Docker, pero vamos a volver a listar las imágenes antes de eso.
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu test a78cf3ea16d8 20 minutes ago 77.8MBnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Si vemos, nos está diciendo que la imagen que acabamos de crear pertenece al repositorio de ubuntu, pero nosotros no tenemos acceso al repositorio de ubuntu, por lo que en Docker Hub nos tenemos que hacer una cuenta para poder subir la imagen a nuestro repositorio. En mi caso, mi repositorio se llama maximofn, por lo que cambio el repositorio de la imagen mediante el comando tag, indicándole la imagen a la que queremos cambiar de repositorio y el nuevo repositorio. En el nuevo repositorio se suele indicar el nombre del repositorio seguido del tipo de imagen y el tag, en mi caso maximofn/ubuntu:test
InputPython!docker tag ubuntu:test maximofn/ubuntu:testCopied
Si ahora volvemos a listar las imágenes
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu test a78cf3ea16d8 24 minutes ago 77.8MBmaximofn/ubuntu test a78cf3ea16d8 24 minutes ago 77.8MBnginx latest 2d389e545974 8 hours ago 142MBubuntu latest 2dc39ba059dc 11 days ago 77.8MBubuntu 20.04 a0ce5a295b63 11 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Ahora debemos loguearnos dentro de Docker Hub para poder subir la imagen, para ello usamos el comando login
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you do not have a Docker ID, head over to https://hub.docker.com to create one.
Username: maximofn
Password:
Login SucceededAhora podemos subir la imagen mediante el comando push
InputPython!docker push maximofn/ubuntu:testCopied
The push refers to repository [docker.io/maximofn/ubuntu]06994357: Preparing06994357: Pushed from library/ubuntu test: digest: sha256:318d83fc3c35ff930d695b0dc1c5ad1b0ea54e1ec6e3478b8ca85c05fd793c4e size: 735
Ha subido solo la primera capa, la segunda, como la usé a raíz de la imagen de Ubuntu, lo que hace es colocar un puntero a esa imagen para no tener capas subidas más de una vez
Hay que tener en cuenta que este repositorio es público, por lo que no debes subir imágenes con datos sensibles. Además, si una imagen no tiene uso en 6 meses será borrada
El sistema de capas
Mediante el comando history podemos ver las capas de una imagen. Si vemos las capas de la imagen que acabamos de crear, usamos docker history ubuntu:test
InputPython!docker history ubuntu:testCopied
IMAGE CREATED CREATED BY SIZE COMMENTa78cf3ea16d8 3 minutes ago /bin/sh -c touch /test.txt 0B2dc39ba059dc 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B<missing> 12 days ago /bin/sh -c #(nop) ADD file:a7268f82a86219801… 77.8MB
Vemos que la primera capa tiene el comando que hemos introducido en el Dockerfile, además dice que ha sido creada hace 3 minutos. Sin embargo, el resto de capas fueron creadas hace 12 días, y son las capas de la imagen de Ubuntu de la que nos hemos basado
Al Dockerfile que hemos creado anteriormente le añadimos la línea
RUN rm /test.txtAl final el Dockerfile queda así:
FROM ubuntu:latest
RUN touch /test.txt
RUN rm /test.txtSi volvemos a compilar, vemos qué pasa
InputPython!docker build -t ubuntu:test ./dockerImagesCopied
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:latest---> 2dc39ba059dcStep 2/3 : RUN touch /test.txt---> Using cache---> a78cf3ea16d8Step 3/3 : RUN rm /test.txt---> Running in c2e6887f2025Removing intermediate container c2e6887f2025---> 313243a9b573Successfully built 313243a9b573Successfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Como vemos, hay una capa más con la nueva línea que hemos agregado. Si volvemos a ver las capas de la imagen con history
InputPython!docker history ubuntu:testCopied
IMAGE CREATED CREATED BY SIZE COMMENT313243a9b573 About a minute ago /bin/sh -c rm /test.txt 0Ba78cf3ea16d8 3 minutes ago /bin/sh -c touch /test.txt 0B2dc39ba059dc 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B<missing> 12 days ago /bin/sh -c #(nop) ADD file:a7268f82a86219801… 77.8MB
Vemos que las primeras capas son iguales a las de antes y ha añadido una nueva capa con el nuevo comando
Búsqueda en Docker Hub
No hace falta meterse en la página de docker hub para buscar imágenes, se puede hacer desde la terminal. Para ello usamos el comando docker search <image name>
InputPython!docker search ubuntuCopied
NAME DESCRIPTION STARS OFFICIAL AUTOMATEDubuntu Ubuntu is a Debian-based Linux operating sys… 16425 [OK]websphere-liberty WebSphere Liberty multi-architecture images … 297 [OK]open-liberty Open Liberty multi-architecture images based… 62 [OK]neurodebian NeuroDebian provides neuroscience research s… 104 [OK]ubuntu-debootstrap DEPRECATED; use "ubuntu" instead 52 [OK]ubuntu-upstart DEPRECATED, as is Upstart (find other proces… 115 [OK]ubuntu/nginx Nginx, a high-performance reverse proxy & we… 98ubuntu/squid Squid is a caching proxy for the Web. Long-t… 66ubuntu/cortex Cortex provides storage for Prometheus. Long… 4ubuntu/apache2 Apache, a secure & extensible open-source HT… 60ubuntu/kafka Apache Kafka, a distributed event streaming … 35ubuntu/mysql MySQL open source fast, stable, multi-thread… 53ubuntu/bind9 BIND 9 is a very flexible, full-featured DNS… 62ubuntu/prometheus Prometheus is a systems and service monitori… 51ubuntu/zookeeper ZooKeeper maintains configuration informatio… 12ubuntu/postgres PostgreSQL is an open source object-relation… 31ubuntu/redis Redis, an open source key-value store. Long-… 19ubuntu/grafana Grafana, a feature rich metrics dashboard & … 9ubuntu/memcached Memcached, in-memory keyvalue store for smal… 5ubuntu/dotnet-aspnet Chiselled Ubuntu runtime image for ASP.NET a… 11ubuntu/dotnet-deps Chiselled Ubuntu for self-contained .NET & A… 11ubuntu/prometheus-alertmanager Alertmanager handles client alerts from Prom… 9ubuntu/dotnet-runtime Chiselled Ubuntu runtime image for .NET apps… 10ubuntu/cassandra Cassandra, an open source NoSQL distributed … 2ubuntu/telegraf Telegraf collects, processes, aggregates & w… 4
Uso de Docker para crear aplicaciones
Exposición de puertos
Antes vimos cómo podíamos vincular un puerto de un contenedor a un puerto del ordenador (-p 8080:80). Pero para que eso sea posible, a la hora de crear la imagen hay que exponer el puerto, esto se hace añadiendo al Dockerfile la línea EXPOSE <port>, en el caso de antes
EXPOSE 80O usar imágenes como base que ya tengan puertos expuestos
Reuso del caché de capas al compilar
Cuando compilamos una imagen, si alguna de las capas que hemos definido ya ha sido compilada antes, Docker lo detecta y las usa, no las vuelve a compilar. Si volvemos a compilar la imagen que hemos definido en el Dockerfile, ahora tardará muy poco, porque todas las capas ya están compiladas y Docker no las vuelve a compilar
InputPython!docker build -t ubuntu:test ./dockerImagesCopied
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:latest---> 2dc39ba059dcStep 2/3 : RUN touch /test.txt---> Using cache---> a78cf3ea16d8Step 3/3 : RUN rm /test.txt---> Using cache---> 313243a9b573Successfully built 313243a9b573Successfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
En la segunda y tercera capa aparece el texto Using cache
Como esto es un cuaderno Jupyter a la hora de ejecutar las celdas te da la información del tiempo que tardan en ejecutarse, la vez anterior que compilé la imagen tardó 1,4 segundos, mientras que ahora ha tardado 0,5 segundos
Pero si ahora cambio el Dockerfile, y en la primera línea, donde decía que nos basábamos en la última versión de Ubuntu y cambiamos a la versión 20.04
FROM ubuntu:20.04Al final el Dockerfile queda así:
FROM ubuntu:20.04
RUN touch /test.txt
RUN rm /test.txtSi volvemos a compilar, tardará mucho más
InputPython!docker build -t ubuntu:test ./dockerImagesCopied
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:20.04---> a0ce5a295b63Step 2/3 : RUN touch /test.txt---> Running in a40fe8df2c0dRemoving intermediate container a40fe8df2c0d---> 0bb9b452c11fStep 3/3 : RUN rm /test.txt---> Running in 2e14919f3685Removing intermediate container 2e14919f3685---> fdc248fa833bSuccessfully built fdc248fa833bSuccessfully tagged ubuntu:testUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Ha tardado 1,9 segundos y ya no aparece el texto Using cache
Al cambiar la primera capa, Docker vuelve a compilar todas las capas. Esto puede ser un problema porque a la hora de desarrollar código se puede dar el siguiente caso
- Desarrollamos el código en nuestro ordenador
- Al construir la imagen, copiamos todo el código de nuestro ordenador al contenedor
- Luego le pedimos a la imagen que instale las bibliotecas necesarias
Esto puede hacer que, al cambiar cualquier parte del código, al tener que volver a compilar la imagen, la capa en la que se instalan las librerías se tenga que volver a compilar, ya que ha cambiado una capa anterior
Para solucionar esto, la idea sería que, a la hora de crear la imagen, primero pidamos que se instalen las librerías y luego que se copie el código de nuestro ordenador al contenedor. Así, cada vez que cambiemos el código y volvamos a compilar la imagen, solo se recompilará la capa en la que se copia el código, por lo que la compilación será más rápida
Podrás pensar que es mejor compartir una carpeta entre el host y el contenedor (bind mount) donde tendremos el código y así no hace falta volver a compilar la imagen cada vez que cambiemos el código. Y la respuesta es que es verdad, solo he puesto este ejemplo porque es muy fácil de entender, pero es para escenificar que a la hora de crear imágenes hay que pensar bien, de manera que si hace falta volver a compilarla, recompila el mínimo número de capas
Escribir correctamente un Dockerfile
Como hemos visto, Docker no vuelve a compilar capas de un Dockerfile si ya las ha compilado antes, por lo que las carga de caché. Vamos a ver cómo tiene que ser la forma correcta de escribir un Dockerfile para aprovecharnos de esto
Vamos a partir de este Dockerfile para ir comentando posibles correcciones
FROM ubuntu
COPY ./sourceCode /sourceCode
RUN apt-get update
RUN apt-get install -y python3 ssh
CMD ["python3", "/sourceCode/sourceApp/app.py"]Como se puede ver, se parte de una imagen de Ubuntu, se copia la carpeta con el código, se actualizan los repositorios, se instala Python, se instala también ssh y se corre la aplicación
Copiar el código antes de la ejecución
Como hemos dicho antes, si primero copiamos el código y luego instalamos Python, cada vez que hagamos un cambio en el código y compilemos la imagen la compilará entera, pero si copiamos el código después de instalar Python, cada vez que cambiemos el código y compilemos la imagen, solo compilará desde la copia del código y no volverá a instalar Python, por lo que el Dockerfile debería pasar a ser así
FROM ubuntu
RUN apt-get update
RUN apt-get install -y python3 ssh
COPY ./sourceCode /sourceCode
CMD ["python3", "/sourceCode/sourceApp/app.py"]Copiar solo el código necesario
Estamos copiando la carpeta con todo el código, pero a lo mejor dentro tenemos código que no necesitamos, por lo que hay que copiar solo el código que de verdad necesitemos para la aplicación, de esta manera la imagen ocupará menos memoria. De modo que el Dockerfile quedaría así
FROM ubuntu
RUN apt-get update
RUN apt-get install -y python3 ssh
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Actualizar repositorios e instalar Python en la misma línea
Estamos actualizando los repositorios en una línea y en otra instalando python3.
FROM ubuntu
RUN apt-get update && apt-get install -y python3 ssh
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]No instalar ssh
Habíamos instalado ssh en la imagen para poder debuggear en caso de necesitarlo, pero eso hace que la imagen ocupe más memoria. En caso de necesitar debuggear, deberíamos entrar en el contenedor, instalar ssh y, a continuación, debuggear. Por lo que quitamos la instalación de ssh
FROM ubuntu
RUN apt-get update && apt-get install -y python3
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Usar --no-install-recommends
Cuando instalamos algo en Ubuntu instala paquetes recomendados, pero que no necesitamos, por lo que la imagen ocupa más espacio. Así que para evitarlo añadimos a la instalación --no-install-recommends
FROM ubuntu
RUN apt-get update && apt-get install -y python3 --no-install-recommends
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Borrar lista de repositorios actualizados
Hemos actualizado la lista de repositorios y hemos instalado python, pero una vez hecho ya no necesitamos la lista de repositorios actualizados, porque lo único que harán será que la imagen ocupe más, de modo que los eliminamos después de instalar python y en su misma línea
FROM ubuntu
RUN apt-get update && apt-get install -y python3 --no-install-recommends && rm -rf /var/lib/apt/lists/*
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Usar una imagen de Python
Todo lo que hemos hecho de actualizar la lista de paquetes e instalar Python no es necesario, ya que ya existen imágenes de Python sobre Ubuntu, que seguramente también han seguido buenas prácticas, que incluso lo hayan hecho mejor que nosotros y que ha sido escaneada en busca de vulnerabilidades por Docker Hub. Por lo que quitamos todo eso y partimos de una imagen de Python
FROM python
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Especificar la imagen de Python
Al no especificar la imagen de Python se está bajando la última, pero en función de cuándo construyas el contenedor se puede bajar una u otra, por lo que hay que añadir el tag con la versión de Python que se quiere
FROM python:3.9.18
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Elegir un tag pequeño
Hemos elegido el tag 3.9.18, pero esa versión de Python tiene un montón de librerías que posiblemente no las necesitemos, por lo que podemos usar las versiones 3.9.18-slim que tiene muchas menos librerías instaladas, o la versión 3.9.18-alpine que es una versión de Python sobre Alpine y no sobre Ubuntu. Alpine es una distribución de Linux muy ligera que tiene muy pocos paquetes instalados y que se suele usar mucho en contenedores Docker para que ocupen muy poco espacio
La imagen de Python 3.9.18 ocupa 997 MB, la 3.9.18-slim ocupa 126 MB y la 3.9.18-alpine ocupa 47.8 MB
FROM python:3.9.18-alpine
COPY ./sourceCode/sourceApp /sourceCode/sourceApp
CMD ["python3", "/sourceCode/sourceApp/app.py"]Indicar el workspace
En lugar de indicar la ruta de la imagen /sourceCode/sourceApp establecemos que dicha ruta sea el workspace de la imagen. Así, cuando copiemos el código o ejecutemos la aplicación, no hace falta indicar la ruta
FROM python:3.9.18-alpine
WORKDIR /sourceCode/sourceApp
COPY ./sourceCode/sourceApp .
CMD ["python3", "app.py"]Indicar el workspace
En lugar de indicar la ruta de la imagen /sourceCode/sourceApp establecemos que dicha ruta sea el workspace de la imagen. Así cuando copiemos el código o ejecutemos la aplicación no hace falta indicar la ruta
FROM python:3.9.18-alpine
WORKDIR /sourceCode/sourceApp
COPY ./sourceCode/sourceApp .
CMD ["python3", "app.py"]Código compartido en una carpeta bind mount
Habíamos creado una carpeta llamada dockerHostFolder en la que habíamos compartido archivos entre el host y un contenedor. Dentro, además, deberían haber tres archivos
InputPython!ls dockerHostFolderCopied
bindFile.txt fileExtract.txt text.txt
Vamos a aprovechar el archivo text.txt para ver eso. Vamos a ver qué hay dentro de text.txt
InputPython!cat dockerHostFolder/text.txtCopied
No hay salida, el archivo está vacío. Vamos a crear de nuevo un contenedor de Ubuntu compartiendo la carpeta dockerHostFolder
InputPython!docker run --name alwaysup -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/nullCopied
24adbded61f507cdf7f192eb5e246e43ee3ffafc9944b7c57918eb2d547dff19
Vemos que el contenedor está en ejecución
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES24adbded61f5 ubuntu "tail -f /dev/null" 16 seconds ago Up 15 seconds alwaysup
Entramos en el contenedor, vemos que está text.txt y que está vacío
$ docker exec -it alwaysup bash
root@24adbded61f5:/# ls dockerContainerFolder/
bindFile.txt fileExtract.txt text.txt
root@24adbded61f5:/# cat dockerContainerFolder/text.txt
root@24adbded61f5:/# Ahora abrimos en el host el archivo text.txt con el editor de textos que queramos, escribimos Hola mundo y guardamos. Si ahora vemos qué hay dentro del archivo en el contenedor veremos el mismo texto
root@24adbded61f5:/# cat dockerContainerFolder/text.txt
Hola mundoAhora editamos el archivo en el contenedor y salimos del contenedor
root@24adbded61f5:/# echo hola contenedor > dockerContainerFolder/text.txt
root@24adbded61f5:/# cat dockerContainerFolder/text.txt
hola contenedor
root@24adbded61f5:/# exit
exitSi miramos el archivo en el host, veremos el texto que escribimos en el contenedor
InputPython!cat dockerHostFolder/text.txtCopied
hola contenedor
Borramos el contenedor
InputPython!docker rm -f alwaysupCopied
alwaysup
Conectar contenedores por red
En caso de que queramos tener varios contenedores corriendo y queramos que se comuniquen, podemos hacer que se comuniquen por red. Docker nos da la posibilidad de hacer eso mediante sus redes virtuales
Vamos a ver qué redes tiene Docker mediante el comando docker network ls
InputPython!docker network lsCopied
NETWORK ID NAME DRIVER SCOPEde6e8b7b737e bridge bridge localda1f5f6fccc0 host host locald3b0d93993c0 none null local
Vemos que por defecto Docker tiene tres redes
- bridge: Está por retrocompatibilidad con versiones anteriores, pero no deberíamos usarla ya
- host: Es la red del host
- none: Esta es la opción que debemos usar si queremos que un contenedor no tenga acceso a Internet
Podemos crear redes nuevas a las que otros contenedores se puedan conectar, para ello usamos el comando docker network create <name>, para que otros contenedores se puedan conectar además debemos añadir la opción --attachable
InputPython!docker network create --attachable myNetworkCopied
2f6f3ddbfa8642e9f6819aa0965c16339e9e910be7bcf56ebb718fcac324cc27
Podemos inspeccionarla mediante el comando docker network inspect <name>
InputPython!docker network inspect myNetworkCopied
[{"Name": "myNetwork","Id": "2f6f3ddbfa8642e9f6819aa0965c16339e9e910be7bcf56ebb718fcac324cc27","Created": "2022-09-14T15:20:08.539830161+02:00","Scope": "local","Driver": "bridge","EnableIPv6": false,"IPAM": {"Driver": "default","Options": {},"Config": [{"Subnet": "172.18.0.0/16","Gateway": "172.18.0.1"}]},"Internal": false,"Attachable": true,"Ingress": false,"ConfigFrom": {"Network": ""},"ConfigOnly": false,"Containers": {},"Options": {},"Labels": {}}]
Ahora tenemos que crear dos contenedores para que se puedan comunicar.
Vamos a crear un nuevo contenedor, que llamaremos container1, con una carpeta compartida y que en su interior se llamará folder1
InputPython!docker run --name container1 -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/folder1 ubuntu tail -f /dev/nullCopied
a5fca8ba1e4ff0a67002f8f1b8cc3cd43185373c2a7e295546f774059ad8dd1a
Ahora creamos otro contenedor, llamado container2, con otra carpeta compartida, pero que se llame folder2
InputPython!docker run --name container2 -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/folder2 ubuntu tail -f /dev/nullCopied
6c8dc18315488ef686f7548516c19b3d716728dd8a173cdb889ec0dd082232f9
Vemos los contenedores corriendo y vemos que están los dos
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES6c8dc1831548 ubuntu "tail -f /dev/null" 3 seconds ago Up 2 seconds container2a5fca8ba1e4f ubuntu "tail -f /dev/null" 4 seconds ago Up 3 seconds container1
Ahora tenemos que conectar los contenedores a la red, para ello usamos el comando docker network connect <network name> <container name>
InputPython!docker network connect myNetwork container1Copied
InputPython!docker network connect myNetwork container2Copied
Para comprobar que se han conectado correctamente podemos inspeccionar la red, pero filtrando por los contenedores conectados
$ docker network inspect --format '{{.Containers}}' myNetwork
map
[ 6c8dc18315488ef686f7548516c19b3d716728dd8a173cdb889ec0dd082232f9:
{
container2
f828d211e894f7a5a992ce41a2a0def8e2424e9737fb4e1485fc09cc2d607b69
02:42:ac:12:00:03
172.18.0.3/16
}
a5fca8ba1e4ff0a67002f8f1b8cc3cd43185373c2a7e295546f774059ad8dd1a:
{
container1
cff762e6286ebc169804b2a675bbff904102de796751d367c18d4b490c994c45
02:42:ac:12:00:02
172.18.0.2/16
}
]Como podemos ver, el contenedor container1 tiene la IP 172.18.0.2 y el contenedor container2 tiene la IP 172.18.0.3
Nos metemos dentro del contenedor container1 e instalamos ping
$ docker exec -it container1 bash
root@a5fca8ba1e4f:/# apt update
...
root@a5fca8ba1e4f:/# apt install iputils-ping
...
root@a5fca8ba1e4f:/#Nos metemos dentro del contenedor container2 e instalamos ping
$ docker exec -it container2 bash
root@a5fca8ba1e4f:/# apt update
...
root@a5fca8ba1e4f:/# apt install iputils-ping
...
root@a5fca8ba1e4f:/#Ahora desde el contenedor container1 hacemos un ping a la IP 172.18.0.3, que pertenece al contenedor container2
root@a5fca8ba1e4f:/# ping 172.18.0.3
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.115 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from 172.18.0.3: icmp_seq=3 ttl=64 time=0.056 ms
64 bytes from 172.18.0.3: icmp_seq=4 ttl=64 time=0.060 ms
^C
--- 172.18.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3068ms
rtt min/avg/max/mdev = 0.049/0.070/0.115/0.026 msY desde el contenedor container2 hacemos un ping a la IP 172.18.0.2, que pertenece al contenedor container1
root@6c8dc1831548:/# ping 172.18.0.2
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.076 ms
64 bytes from 172.18.0.2: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 172.18.0.2: icmp_seq=3 ttl=64 time=0.049 ms
64 bytes from 172.18.0.2: icmp_seq=4 ttl=64 time=0.051 ms
^C
--- 172.18.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3074ms
rtt min/avg/max/mdev = 0.045/0.055/0.076/0.012 msPero hay una cosa mejor que nos permite hacer Docker: si no me sé la IP del contenedor al que me quiero conectar, en vez de escribir su IP puedo escribir su nombre
Ahora desde el contenedor container1 hacemos un ping a la IP de container2
root@a5fca8ba1e4f:/# ping container2
PING container2 (172.18.0.3) 56(84) bytes of data.
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=1 ttl=64 time=0.048 ms
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=2 ttl=64 time=0.050 ms
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=3 ttl=64 time=0.052 ms
64 bytes from container2.myNetwork (172.18.0.3): icmp_seq=4 ttl=64 time=0.053 ms
^C
--- container2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3071ms
rtt min/avg/max/mdev = 0.048/0.050/0.053/0.002 msComo vemos, docker sabe que la IP del contenedor container2 es la 172.18.0.3
Y desde el contenedor container2 hacemos un ping a la IP de container1
root@6c8dc1831548:/# ping container1
PING container1 (172.18.0.2) 56(84) bytes of data.
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=3 ttl=64 time=0.052 ms
64 bytes from container1.myNetwork (172.18.0.2): icmp_seq=4 ttl=64 time=0.056 ms
^C
--- container1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3057ms
rtt min/avg/max/mdev = 0.051/0.054/0.058/0.003 msComo vemos, docker sabe que la IP del contenedor container1 es la 172.18.0.2
Salimos de los contenedores y los borramos
InputPython!docker rm -f container1 container2Copied
container1container2
Borramos también la red que hemos creado
InputPython!docker network rm myNetworkCopied
myNetwork
Uso de GPUs
Para poder usar las GPUs del host dentro de los contenedores Docker es necesario realizar los pasos descritos en la página de instalación de Nvidia container toolkit
Configurar el repositorio y la llave GPG
Tenemos que configurar el repositorio de nvidia container toolkit y la llave GPG, para ello ejecutamos el siguiente comando en la consola
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.listInstalación de nvidia container toolkit
Una vez hemos actualizado el repositorio y la llave, actualizamos los repositorios mediante el comando``` bash
sudo apt update
E instalamos `nvidia container toolkit`bash
sudo apt install -y nvidia-docker2
```
Reinicio de docker
Una vez hayamos terminado, tenemos que reiniciar el demonio de Docker mediante
sudo systemctl restart dockerUso de GPUs
Ahora que hemos configurado Docker para poder usar las GPUs del host dentro de los contenedores, lo podemos probar mediante la opción --gpus all. Si se tiene más de una GPU y solo se quiere usar 1 habría que especificarlo, pero de momento aquí solo explicamos cómo usar todas
Creamos un contenedor que no se va a ejecutar en segundo plano, sino que lo que va a hacer es ejecutar el comando nvidia-smi para que podamos ver si tiene acceso a las GPUs
InputPython!docker run --name container_gpus --gpus all ubuntu nvidia-smiCopied
Unable to find image 'ubuntu:latest' locallylatest: Pulling from library/ubuntu6a12be2b: Pull complete .54MB/29.54MBBDigest: sha256:aabed3296a3d45cede1dc866a24476c4d7e093aa806263c27ddaadbdce3c1054Status: Downloaded newer image for ubuntu:latestMon Sep 4 07:10:36 2023+-----------------------------------------------------------------------------+| NVIDIA-SMI 510.39.01 Driver Version: 510.39.01 CUDA Version: 11.6 ||-------------------------------+----------------------+----------------------+| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. || | | MIG M. ||===============================+======================+======================|| 0 Quadro T1000 On | 00000000:01:00.0 Off | N/A || N/A 44C P0 15W / N/A | 9MiB / 4096MiB | 0% Default || | | N/A |+-------------------------------+----------------------+----------------------++-----------------------------------------------------------------------------+| Processes: || GPU GI CI PID Type Process name GPU Memory || ID ID Usage ||=============================================================================|| 0 N/A N/A 2545 G 4MiB || 0 N/A N/A 3421 G 4MiB |+-----------------------------------------------------------------------------+
Borramos el contenedor
InputPython!doker rm container_gpusCopied
---
➡️ **Continúa en la Parte 2: Docker Compose y temas avanzados**, donde orquestarás varios contenedores a la vez y profundizarás en Docker.