Docker (1/2): contenedores, imágenes y aplicaciones

Docker (1/2): contenedores, imágenes y aplicaciones

📚 **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

Contenedoreslink image 1

Hello worldlink image 2

Ejecutar el primer contenedor tipo Hello world con el comando docker run hello-world

	
< > Input
Python
!docker run hello-world
Copied
>_ Output
			
Unable to find image 'hello-world:latest' locally
>_ Output
			
latest: Pulling from library/hello-world
85e32844: Pull complete 457kB/2.457kBBDigest: sha256:dcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1c
Status: Downloaded newer image for hello-world:latest
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 the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share 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

	
< > Input
Python
!docker run hello-world
Copied
>_ Output
			
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 the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share 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

	
< > Input
Python
!docker ps
Copied
>_ Output
			
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

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1efb51bbbf38 hello-world "/hello" 10 seconds ago Exited (0) 9 seconds ago strange_thompson
5f5705e7603e 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

	
< > Input
Python
!docker inspect 1efb51bbbf38
Copied
>_ Output
			
[
{
"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

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1efb51bbbf38 hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago strange_thompson
5f5705e7603e 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

	
< > Input
Python
!docker inspect strange_thompson
Copied
>_ Output
			
[
{
"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

	
< > Input
Python
!docker run --name hello_world hello-world
Copied
>_ Output
			
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 the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share 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>

	
< > Input
Python
!docker rename hello_world hello_world2
Copied

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>

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f432c9c2ca21 hello-world "/hello" 9 seconds ago Exited (0) 8 seconds ago hello_world2
1efb51bbbf38 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago strange_thompson
5f5705e7603e hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago laughing_jang
	
< > Input
Python
!docker rm hello_world2
Copied
>_ Output
			
hello_world2

Si volvemos a ver la lista de contenedores, el contenedor hello_world2 ya no estará

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1efb51bbbf38 hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago strange_thompson
5f5705e7603e 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

	
< > Input
Python
!docker container prune
Copied
>_ Output
			
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

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

El modo interactivolink image 3

Vamos a ejecutar un Ubuntu mediante el comando docker run ubuntu

	
< > Input
Python
!docker run ubuntu
Copied
>_ Output
			
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
Digest: sha256:20fa2d7bb4de7723f542be5923b06c4d704370f0390e4ae9e1c833c8785644c1[1A
Status: 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.

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Listamos ahora todos los contenedores

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
da16b3a85178 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

	
< > Input
Python
!docker run -it ubuntu
Copied
>_ Output
			
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

	
< > Input
Python
!root@5b633e9d838f:/# cat /etc/lsb-release
Copied
>_ Output
			
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.1 LTS"

Si abrimos otra terminal y vemos la lista de contenedores, ahora sí aparecerá el contenedor corriendo Ubuntu

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b633e9d838f 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

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b633e9d838f ubuntu "bash" 3 minutes ago Up 3 minutes funny_mirzakhani
da16b3a85178 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.

	
< > Input
Python
!root@5b633e9d838f:/# exit
Copied
>_ Output
			
exit

Si ejecutamos docker ps, el contenedor ya no aparece

	
< > Input
Python
!docker ps
Copied
>_ Output
			
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ó

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b633e9d838f ubuntu "bash" 4 minutes ago Exited (0) 27 seconds ago funny_mirzakhani
da16b3a85178 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 contenedorlink image 4

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

	
< > Input
Python
!docker run --name alwaysup -d ubuntu tail -f /dev/null
Copied
>_ Output
			
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

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd 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

	
< > Input
Python
!docker exec -it alwaysup bash
Copied
>_ Output
			
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.

	
< > Input
Python
!ps -aux
Copied
>_ Output
			
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2820 1048 ? Ss 13:04 0:00 tail -f /dev/null
root 7 0.0 0.0 4628 3796 pts/0 Ss 13:04 0:00 bash
root 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

	
< > Input
Python
!exit
Copied
>_ Output
			
exit
	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd 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>

	
< > Input
Python
!docker stop alwaysup
Copied
>_ Output
			
alwaysup

Si ahora volvemos a listar los contenedores encendidos, ya no aparece el contenedor con Ubuntu

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Y si listamos todos los contenedores, aparece el contenedor con Ubuntu, y su estado Exited

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd ubuntu "tail -f /dev/null" 14 minutes ago Exited (137) About a minute ago alwaysup
5b633e9d838f ubuntu "bash" 19 minutes ago Exited (0) 15 minutes ago funny_mirzakhani
da16b3a85178 ubuntu "bash" 20 minutes ago Exited (0) 20 minutes ago hardcore_kare

También podemos pausar un contenedor mediante el comando docker pause <name>

	
< > Input
Python
!docker run --name alwaysup -d ubuntu tail -f /dev/null
Copied
>_ Output
			
8282eaf9dc3604fa94df206b2062287409cc92cbcd203f1a018742b5c171c9e4

Ahora lo pausamos

	
< > Input
Python
!docker pause alwaysup
Copied
>_ Output
			
alwaysup

Si volvemos a ver todos los contenedores, vemos que el contenedor con Ubuntu está pausado

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8282eaf9dc36 ubuntu "tail -f /dev/null" 41 seconds ago Up 41 seconds (Paused) alwaysup
5b633e9d838f ubuntu "bash" 19 minutes ago Exited (0) 15 minutes ago funny_mirzakhani
da16b3a85178 ubuntu "bash" 20 minutes ago Exited (0) 20 minutes ago hardcore_kare

Contenedores de un solo usolink image 5

Si a la hora de ejecutar un contenedor, ponemos la opción --rm, ese contenedor se va a borrar cuando termine de ejecutarse.

	
< > Input
Python
!docker run --rm --name autoremove ubuntu:latest
Copied

Si ahora vemos qué contenedores tenemos

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Vemos que no está el contenedor que acabamos de crear

Exponer contenedores al mundo exteriorlink image 6

Vamos a crear un nuevo contenedor con un servidor

	
< > Input
Python
!docker run -d --name proxy nginx
Copied
>_ Output
			
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
f1ad4ce1: Pulling fs layer
b079d0f8: Pulling fs layer
5fbbebc6: Pulling fs layer
ffdd25f4: Pulling fs layer
32c8fba2: Pulling fs layer
24b8ba39: Pull complete 393kB/1.393kBB[5ADigest: sha256:2888a97f7c7d498bbcc47ede1ad0f6ced07d72dfd181071dde051863f1f79d7b
Status: Downloaded newer image for nginx:latest
1a530e04f14be082811b72ea8b6ea5a95dad3037301ee8a1351a0108ff8d3b30

Esto crea un servidor, vamos a volver a listar los contenedores que están corriendo

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1a530e04f14b 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

	
< > Input
Python
!docker stop proxy
Copied
>_ Output
			
proxy

Si listamos los contenedores, no aparece corriendo

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Lo borramos para volver a crearlo

	
< > Input
Python
!docker rm proxy
Copied
>_ Output
			
proxy

Si listamos todos los contenedores ya no están

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd ubuntu "tail -f /dev/null" 19 minutes ago Exited (137) 5 minutes ago alwaysup
5b633e9d838f ubuntu "bash" 24 minutes ago Exited (0) 20 minutes ago funny_mirzakhani
da16b3a85178 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>

	
< > Input
Python
!docker run -d --name proxy -p 8080:80 nginx
Copied
>_ Output
			
c199235e42f76a30266f6e1af972e0a59811806eb3d3a9afdd873f6fa1785eae

Listamos los contenedores

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c199235e42f7 nginx "/docker-entrypoint.…" 22 seconds ago Up 21 seconds 0.0.0.0:8080-&gt;80/tcp, :::8080-&gt;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

	
< > Input
Python
!docker logs proxy
Copied
>_ Output
			
/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.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-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 up
2022/09/13 13:24:06 [notice] 1#1: using the "epoll" event method
2022/09/13 13:24:06 [notice] 1#1: nginx/1.23.1
2022/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-generic
2022/09/13 13:24:06 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/09/13 13:24:06 [notice] 1#1: start worker processes
2022/09/13 13:24:06 [notice] 1#1: start worker process 31
2022/09/13 13:24:06 [notice] 1#1: start worker process 32
2022/09/13 13:24:06 [notice] 1#1: start worker process 33
2022/09/13 13:24:06 [notice] 1#1: start worker process 34
2022/09/13 13:24:06 [notice] 1#1: start worker process 35
2022/09/13 13:24:06 [notice] 1#1: start worker process 36
2022/09/13 13:24:06 [notice] 1#1: start worker process 37
2022/09/13 13:24:06 [notice] 1#1: start worker process 38
2022/09/13 13:24:06 [notice] 1#1: start worker process 39
2022/09/13 13:24:06 [notice] 1#1: start worker process 40
2022/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'×ü[€ïºåÇè÷&amp;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

	
< > Input
Python
!docker logs -f proxy
Copied

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

	
< > Input
Python
!docker logs --tail 10 proxy
Copied
>_ Output
			
2022/09/13 13:24:06 [notice] 1#1: start worker process 41
2022/09/13 13:24:06 [notice] 1#1: start worker process 42
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 "-" "-" "-"
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'×ü[€ïºåÇè÷&amp;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

	
< > Input
Python
!docker logs --tail -t 10 proxy
Copied
>_ Output
			
2022-09-13T13:24:06.573362728Z 2022/09/13 13:24:06 [notice] 1#1: start worker process 41
2022-09-13T13:24:06.651127107Z 2022/09/13 13:24:06 [notice] 1#1: start worker process 42
2022-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'×ü[€ïºåÇè÷&amp;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

	
< > Input
Python
!docker rm -f proxy
Copied
>_ Output
			
proxy
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd ubuntu "tail -f /dev/null" 26 minutes ago Exited (137) 13 minutes ago alwaysup
5b633e9d838f ubuntu "bash" 31 minutes ago Exited (0) 27 minutes ago funny_mirzakhani
da16b3a85178 ubuntu "bash" 32 minutes ago Exited (0) 32 minutes ago hardcore_kare

Datos en Dockerlink image 7

Bind mountslink image 8

Vamos a ver los contenedores que tenemos parados

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd ubuntu "tail -f /dev/null" 26 minutes ago Exited (137) 13 minutes ago alwaysup
5b633e9d838f ubuntu "bash" 31 minutes ago Exited (0) 28 minutes ago funny_mirzakhani
da16b3a85178 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

	
< > Input
Python
!docker rm funny_mirzakhani
Copied
>_ Output
			
funny_mirzakhani
	
< > Input
Python
!docker rm hardcore_kare
Copied
>_ Output
			
hardcore_kare
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce4d60427dcd 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

	
< > Input
Python
!docker start alwaysup
Copied
>_ Output
			
alwaysup

Nos metemos otra vez dentro del

	
< > Input
Python
!docker exec -it alwaysup bash
Copied
>_ Output
			
root@ce4d60427dcd:/#

En el contenedor, puedo crear una nueva carpeta que se llame dockerfolder

	
< > Input
Python
!mkdir dockerfolder
Copied

Si listamos los archivos aparecerá la nueva carpeta

	
< > Input
Python
!ls
Copied
>_ Output
			
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

	
< > Input
Python
!exit
Copied
>_ Output
			
exit

Y lo borramos

	
< > Input
Python
!docker rm -f alwaysup
Copied
>_ Output
			
alwaysup

Si listamos todos los contenedores, ya no aparece el último que hemos creado

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
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

	
< > Input
Python
!mkdir dockerHostFolder
Copied

Vemos que dentro de la carpeta no hay nada

	
< > Input
Python
!ls dockerHostFolder
Copied

Ahora obtenemos nuestra ruta absoluta

	
< > Input
Python
!pwd
Copied
>_ Output
			
/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>

	
< > Input
Python
!docker run -d --name alwaysup -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/null
Copied
>_ Output
			
4ede4512c293bdcc155e9c8e874dfb4a28e5163f4d5c7ddda24ad2863f28921b

Entramos al contenedor, listamos los archivos y ya aparece la carpeta que habíamos creado

	
< > Input
Python
!docker exec -it alwaysup bash
Copied
>_ Output
			
root@4ede4512c293:/#
	
< > Input
Python
root@4ede4512c293:/# ls
Copied
>_ Output
			
bin dev etc lib lib64 media opt root sbin sys usr
boot 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

	
< > Input
Python
root@4ede4512c293:/# cd dockerContainerFolder
Copied
	
< > Input
Python
root@4ede4512c293:/dockerContainerFolder# touch bindFile.txt
Copied
	
< > Input
Python
root@4ede4512c293:/dockerContainerFolder# exit
Copied
>_ Output
			
exit

Vemos qué hay dentro de la carpeta compartida

	
< > Input
Python
!ls dockerHostFolder
Copied
>_ Output
			
bindFile.txt

Pero es más, si borramos el contenedor, el archivo sigue allí

	
< > Input
Python
!docker rm -f alwaysup
Copied
>_ Output
			
alwaysup
	
< > Input
Python
!ls dockerHostFolder
Copied
>_ Output
			
bindFile.txt

Si vuelvo a crear un contenedor compartiendo las carpetas, todos los archivos estarán en el contenedor

	
< > Input
Python
!docker run -d --name alwaysup -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/null
Copied
>_ Output
			
6c021d37ea29d8b23fe5cd4968baa446085ae1756682f65340288b4c851c362d
	
< > Input
Python
!docker exec -it alwaysup bash
Copied
>_ Output
			
root@6c021d37ea29:/#
	
< > Input
Python
!root@6c021d37ea29:/# ls dockerContainerFolder/
Copied
>_ Output
			
bindFile.txt:/#

Eliminamos el contenedor

	
< > Input
Python
!docker rm -f alwaysup
Copied
>_ Output
			
alwaysup
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Volúmeneslink image 9

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

	
< > Input
Python
!docker volume ls
Copied
>_ Output
			
DRIVER VOLUME NAME

Vamos a crear un nuevo volumen para el contenedor de ubuntu, para ello usamos el comando docker volume create <volume name>

	
< > Input
Python
!docker volume create ubuntuVolume
Copied
>_ Output
			
ubuntuVolume

Si volvemos a listar los volúmenes aparecerá el que acabamos de crear

	
< > Input
Python
!docker volume ls
Copied
>_ Output
			
DRIVER VOLUME NAME
local ubuntuVolume

Sin embargo, no aparece como una carpeta en el sistema de archivos del host. Con ls -d */ listamos todas las carpetas

	
< > Input
Python
!ls -d */
Copied
>_ Output
			
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>

	
< > Input
Python
!docker run -d --name alwaysup --mount src=ubuntuVolume,dst=/dockerVolumeFolder ubuntu tail -f /dev/null
Copied
>_ Output
			
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.txt

Está el archivo creado

Insertar y extraer archivos de un contenedorlink image 10

Primero vamos a crear un archivo que queremos copiar dentro de un contenedor

	
< > Input
Python
!touch dockerHostFolder/text.txt
Copied

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
exit

Copiamos 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>

	
< > Input
Python
!docker cp dockerHostFolder/text.txt alwaysup:/folderToCopy
Copied

Volvemos a entrar al contenedor y comprobamos que esté el archivo

$ docker exec -it alwaysup bash
root@42cdcddf4e46:/# ls folderToCopy/
text.txt

Salimos del contenedor

/# exit
exit

Ahora 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>

	
< > Input
Python
!docker cp alwaysup:/folderToCopy/text.txt dockerHostFolder/fileExtract.txt
Copied

Vemos que está en el host

	
< > Input
Python
!ls dockerHostFolder
Copied
>_ Output
			
bindFile.txt fileExtract.txt text.txt

Aunque el contenedor esté parado también se pueden copiar archivos

Por último, borramos el contenedor

	
< > Input
Python
!docker rm -f alwaysup
Copied
>_ Output
			
alwaysup

Imágeneslink image 11

Conceptos fundamentaleslink image 12

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

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 2d389e545974 8 hours ago 142MB
ubuntu latest 2dc39ba059dc 11 days ago 77.8MB
hello-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

	
< > Input
Python
!docker pull ubuntu:20.04
Copied
>_ Output
			
20.04: Pulling from library/ubuntu
Digest: sha256:35ab2bf57814e9ff49e365efd5a5935b6915eede5c7f8581e9e1b85e0eecbe16[1A
Status: Downloaded newer image for ubuntu:20.04
docker.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

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 2d389e545974 8 hours ago 142MB
ubuntu latest 2dc39ba059dc 11 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 11 days ago 72.8MB
hello-world latest feb5d9fea6a5 11 months ago 13.3kB

Crear imágenes mediante Dockerfilelink image 13

Creamos un directorio en el host llamado dockerImages para trabajar en él

	
< > Input
Python
!mkdir dockerImages
Copied

Creamos un archivo Dockerfile con el que crearemos una imagen

	
< > Input
Python
!touch dockerImages/Dockerfile
Copied

Abrimos el archivo creado con nuestro editor preferido y escribimos lo siguiente:

FROM ubuntu:latest

Esto 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.txt

Esto 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.txt

Compilamos 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

	
< > Input
Python
!docker build -t ubuntu:test ./dockerImages
Copied
>_ Output
			
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM ubuntu:latest
---&gt; 2dc39ba059dc
Step 2/2 : RUN touch /test.txt
---&gt; Using cache
---&gt; a78cf3ea16d8
Successfully built a78cf3ea16d8
Successfully tagged ubuntu:test
Use '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

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu test a78cf3ea16d8 8 minutes ago 77.8MB
nginx latest 2d389e545974 8 hours ago 142MB
ubuntu latest 2dc39ba059dc 11 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 11 days ago 72.8MB
hello-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 var

Es 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
exit

Como ya tenemos una imagen, la podríamos subir al Hub de Docker, pero vamos a volver a listar las imágenes antes de eso.

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu test a78cf3ea16d8 20 minutes ago 77.8MB
nginx latest 2d389e545974 8 hours ago 142MB
ubuntu latest 2dc39ba059dc 11 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 11 days ago 72.8MB
hello-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

	
< > Input
Python
!docker tag ubuntu:test maximofn/ubuntu:test
Copied

Si ahora volvemos a listar las imágenes

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu test a78cf3ea16d8 24 minutes ago 77.8MB
maximofn/ubuntu test a78cf3ea16d8 24 minutes ago 77.8MB
nginx latest 2d389e545974 8 hours ago 142MB
ubuntu latest 2dc39ba059dc 11 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 11 days ago 72.8MB
hello-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 Succeeded

Ahora podemos subir la imagen mediante el comando push

	
< > Input
Python
!docker push maximofn/ubuntu:test
Copied
>_ Output
			
The push refers to repository [docker.io/maximofn/ubuntu]
06994357: Preparing
06994357: 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 capaslink image 14

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

	
< > Input
Python
!docker history ubuntu:test
Copied
>_ Output
			
IMAGE CREATED CREATED BY SIZE COMMENT
a78cf3ea16d8 3 minutes ago /bin/sh -c touch /test.txt 0B
2dc39ba059dc 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
&lt;missing&gt; 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.txt

Al final el Dockerfile queda así:

  FROM ubuntu:latest
RUN touch /test.txt
RUN rm /test.txt

Si volvemos a compilar, vemos qué pasa

	
< > Input
Python
!docker build -t ubuntu:test ./dockerImages
Copied
>_ Output
			
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:latest
---&gt; 2dc39ba059dc
Step 2/3 : RUN touch /test.txt
---&gt; Using cache
---&gt; a78cf3ea16d8
Step 3/3 : RUN rm /test.txt
---&gt; Running in c2e6887f2025
Removing intermediate container c2e6887f2025
---&gt; 313243a9b573
Successfully built 313243a9b573
Successfully tagged ubuntu:test
Use '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

	
< > Input
Python
!docker history ubuntu:test
Copied
>_ Output
			
IMAGE CREATED CREATED BY SIZE COMMENT
313243a9b573 About a minute ago /bin/sh -c rm /test.txt 0B
a78cf3ea16d8 3 minutes ago /bin/sh -c touch /test.txt 0B
2dc39ba059dc 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
&lt;missing&gt; 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 Hublink image 15

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>

	
< > Input
Python
!docker search ubuntu
Copied
>_ Output
			
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
ubuntu 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 &amp; we… 98
ubuntu/squid Squid is a caching proxy for the Web. Long-t… 66
ubuntu/cortex Cortex provides storage for Prometheus. Long… 4
ubuntu/apache2 Apache, a secure &amp; extensible open-source HT… 60
ubuntu/kafka Apache Kafka, a distributed event streaming … 35
ubuntu/mysql MySQL open source fast, stable, multi-thread… 53
ubuntu/bind9 BIND 9 is a very flexible, full-featured DNS… 62
ubuntu/prometheus Prometheus is a systems and service monitori… 51
ubuntu/zookeeper ZooKeeper maintains configuration informatio… 12
ubuntu/postgres PostgreSQL is an open source object-relation… 31
ubuntu/redis Redis, an open source key-value store. Long-… 19
ubuntu/grafana Grafana, a feature rich metrics dashboard &amp; … 9
ubuntu/memcached Memcached, in-memory keyvalue store for smal… 5
ubuntu/dotnet-aspnet Chiselled Ubuntu runtime image for ASP.NET a… 11
ubuntu/dotnet-deps Chiselled Ubuntu for self-contained .NET &amp; A… 11
ubuntu/prometheus-alertmanager Alertmanager handles client alerts from Prom… 9
ubuntu/dotnet-runtime Chiselled Ubuntu runtime image for .NET apps… 10
ubuntu/cassandra Cassandra, an open source NoSQL distributed … 2
ubuntu/telegraf Telegraf collects, processes, aggregates &amp; w… 4

Uso de Docker para crear aplicacioneslink image 16

Exposición de puertoslink image 17

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 80

O usar imágenes como base que ya tengan puertos expuestos

Reuso del caché de capas al compilarlink image 18

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

	
< > Input
Python
!docker build -t ubuntu:test ./dockerImages
Copied
>_ Output
			
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:latest
---&gt; 2dc39ba059dc
Step 2/3 : RUN touch /test.txt
---&gt; Using cache
---&gt; a78cf3ea16d8
Step 3/3 : RUN rm /test.txt
---&gt; Using cache
---&gt; 313243a9b573
Successfully built 313243a9b573
Successfully tagged ubuntu:test
Use '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.04

Al final el Dockerfile queda así:

  FROM ubuntu:20.04
RUN touch /test.txt
RUN rm /test.txt

Si volvemos a compilar, tardará mucho más

	
< > Input
Python
!docker build -t ubuntu:test ./dockerImages
Copied
>_ Output
			
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:20.04
---&gt; a0ce5a295b63
Step 2/3 : RUN touch /test.txt
---&gt; Running in a40fe8df2c0d
Removing intermediate container a40fe8df2c0d
---&gt; 0bb9b452c11f
Step 3/3 : RUN rm /test.txt
---&gt; Running in 2e14919f3685
Removing intermediate container 2e14919f3685
---&gt; fdc248fa833b
Successfully built fdc248fa833b
Successfully tagged ubuntu:test
Use '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 Dockerfilelink image 19

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ónlink image 20

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 necesariolink image 21

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ínealink image 22

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 sshlink image 23

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-recommendslink image 24

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 actualizadoslink image 25

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 Pythonlink image 26

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 Pythonlink image 27

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ñolink image 28

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 workspacelink image 29

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 workspacelink image 30

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 mountlink image 31

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

	
< > Input
Python
!ls dockerHostFolder
Copied
>_ Output
			
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

	
< > Input
Python
!cat dockerHostFolder/text.txt
Copied

No hay salida, el archivo está vacío. Vamos a crear de nuevo un contenedor de Ubuntu compartiendo la carpeta dockerHostFolder

	
< > Input
Python
!docker run --name alwaysup -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/dockerContainerFolder ubuntu tail -f /dev/null
Copied
>_ Output
			
24adbded61f507cdf7f192eb5e246e43ee3ffafc9944b7c57918eb2d547dff19

Vemos que el contenedor está en ejecución

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
24adbded61f5 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 mundo

Ahora 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
exit

Si miramos el archivo en el host, veremos el texto que escribimos en el contenedor

	
< > Input
Python
!cat dockerHostFolder/text.txt
Copied
>_ Output
			
hola contenedor

Borramos el contenedor

	
< > Input
Python
!docker rm -f alwaysup
Copied
>_ Output
			
alwaysup

Conectar contenedores por redlink image 32

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

	
< > Input
Python
!docker network ls
Copied
>_ Output
			
NETWORK ID NAME DRIVER SCOPE
de6e8b7b737e bridge bridge local
da1f5f6fccc0 host host local
d3b0d93993c0 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

	
< > Input
Python
!docker network create --attachable myNetwork
Copied
>_ Output
			
2f6f3ddbfa8642e9f6819aa0965c16339e9e910be7bcf56ebb718fcac324cc27

Podemos inspeccionarla mediante el comando docker network inspect <name>

	
< > Input
Python
!docker network inspect myNetwork
Copied
>_ Output
			
[
{
"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

	
< > Input
Python
!docker run --name container1 -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/folder1 ubuntu tail -f /dev/null
Copied
>_ Output
			
a5fca8ba1e4ff0a67002f8f1b8cc3cd43185373c2a7e295546f774059ad8dd1a

Ahora creamos otro contenedor, llamado container2, con otra carpeta compartida, pero que se llame folder2

	
< > Input
Python
!docker run --name container2 -d -v ~/Documentos/web/portafolio/posts/dockerHostFolder:/folder2 ubuntu tail -f /dev/null
Copied
>_ Output
			
6c8dc18315488ef686f7548516c19b3d716728dd8a173cdb889ec0dd082232f9

Vemos los contenedores corriendo y vemos que están los dos

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6c8dc1831548 ubuntu "tail -f /dev/null" 3 seconds ago Up 2 seconds container2
a5fca8ba1e4f 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>

	
< > Input
Python
!docker network connect myNetwork container1
Copied
	
< > Input
Python
!docker network connect myNetwork container2
Copied

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 ms

Y 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 ms

Pero 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 ms

Como 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 ms

Como vemos, docker sabe que la IP del contenedor container1 es la 172.18.0.2

Salimos de los contenedores y los borramos

	
< > Input
Python
!docker rm -f container1 container2
Copied
>_ Output
			
container1
container2

Borramos también la red que hemos creado

	
< > Input
Python
!docker network rm myNetwork
Copied
>_ Output
			
myNetwork

Uso de GPUslink image 33

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 GPGlink image 34

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.list

Instalación de nvidia container toolkitlink image 35

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 dockerlink image 36

Una vez hayamos terminado, tenemos que reiniciar el demonio de Docker mediante

sudo systemctl restart docker

Uso de GPUslink image 37

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

	
< > Input
Python
!docker run --name container_gpus --gpus all ubuntu nvidia-smi
Copied
>_ Output
			
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
6a12be2b: Pull complete .54MB/29.54MBBDigest: sha256:aabed3296a3d45cede1dc866a24476c4d7e093aa806263c27ddaadbdce3c1054
Status: Downloaded newer image for ubuntu:latest
Mon 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

	
< > Input
Python
!doker rm container_gpus
Copied

---

➡️ **Continúa en la Parte 2: Docker Compose y temas avanzados**, donde orquestarás varios contenedores a la vez y profundizarás en Docker.

Serie completa

Preguntas frecuentes

¿Por qué mi contenedor aparece como "Exited (0)" justo después de ejecutar docker run ubuntu?

Cuando lanzas docker run ubuntu sin indicarle ningún proceso, el contenedor arranca el bash de Ubuntu, no le pasas ninguna orden, así que ese bash termina inmediatamente y devuelve el código 0. Como en Docker el contenedor se apaga en cuanto termina su proceso principal, verás el estado Exited (0) al hacer docker ps -a. Para evitarlo tienes dos opciones: ejecutarlo en modo interactivo con docker run -it ubuntu para quedarte dentro del bash, o lanzarlo en segundo plano con un proceso que no termine nunca, por ejemplo docker run --name alwaysup -d ubuntu tail -f /dev/null.

¿Por qué no puedo acceder a mi contenedor nginx en http://localhost:80 aunque docker ps muestre el puerto 80?

Si creaste el contenedor con docker run -d --name proxy nginx, el puerto 80 pertenece a la red interna del contenedor, no al host, por lo que el navegador no llega a él aunque docker ps lo liste. Hay que publicar el puerto explícitamente con la opción -p <puerto host>:<puerto contenedor>, por ejemplo docker run -d --name proxy -p 8080:80 nginx; a partir de ahí docker ps mostrará 0.0.0.0:8080->80/tcp y podrás entrar desde http://0.0.0.0:8080.

Seguir leyendo

Últimos posts -->

¿Has visto estos proyectos?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Aplicación móvil de entrenamiento personal con asistente de IA, biblioteca de ejercicios, seguimiento de rutinas, dieta y medidas corporales

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversacional para cocineros de hoteles y restaurantes. Un cocinero, jefe de cocina o camaeror de un hotel o restaurante puede hablar con el chatbot para obtener información de recetas y menús. Pero además implementa agentes, con los cuales puede editar o crear nuevas recetas o menús

Naviground

Naviground Naviground
Ver todos los proyectos -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres ver alguna charla?

Últimas charlas -->

¿Quieres mejorar con estos tips?

Últimos tips -->

Usa esto en local

Los espacios de Hugging Face nos permite ejecutar modelos con demos muy sencillas, pero ¿qué pasa si la demo se rompe? O si el usuario la elimina? Por ello he creado contenedores docker con algunos espacios interesantes, para poder usarlos de manera local, pase lo que pase. De hecho, es posible que si pinchas en alún botón de ver proyecto te lleve a un espacio que no funciona.

Flow edit

Flow edit Flow edit

Edita imágenes con este modelo de Flow. Basándose en SD3 o FLUX puedes editar cualquier imagen y generar nuevas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos los contenedores -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres entrenar tu modelo con estos datasets?

short-jokes-dataset

HuggingFace

Dataset de chistes en inglés

Uso: Fine-tuning de modelos de generación de texto humorístico

231K filas 2 columnas 45 MB
Ver en HuggingFace →

opus100

HuggingFace

Dataset con traducciones de inglés a español

Uso: Entrenamiento de modelos de traducción inglés-español

1M filas 2 columnas 210 MB
Ver en HuggingFace →

netflix_titles

HuggingFace

Dataset con películas y series de Netflix

Uso: Análisis de catálogo de Netflix y sistemas de recomendación

8.8K filas 12 columnas 3.5 MB
Ver en HuggingFace →
Ver más datasets -->