Docker (2/2): Docker Compose y temas avanzados

Docker (2/2): Docker Compose y temas avanzados

En la primera parte aprendimos a manejar contenedores, los datos y volúmenes, las imágenes, a crear aplicaciones con Docker y a usar GPUs. En este segundo capítulo damos el salto a **Docker Compose** para orquestar varios contenedores y a una serie de **temas avanzados** de Docker. 🐳

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

Docker composelink image 1

Docker compose vs docker-composelink image 2

docker-compose fue una herramienta que se creó para ayudar al mantenimiento de imágenes y contenedores y había que instalarla aparte de docker. Sin embargo, docker lo incorporó en sus últimas versiones y ya no es necesario instalarla; sin embargo, para usarla, en vez de usar el comando docker-compose hay que usar el comando docker compose. En muchos sitios encontrarás información con docker-compose, pero al instalar docker ya te vendrá instalado docker compose, por lo que todo lo que se podía hacer con docker-compose es compatible con docker compose

Docker composelink image 3

Docker Compose es una herramienta de Docker que hace todo lo que hemos visto hasta ahora, pero ahorrándonos tiempo y esfuerzo. Editando un archivo .yml podemos decirle a Docker Compose que cree todos los contenedores que queramos.

Para usarlo una vez no habrá mucha diferencia de escribir todos los comandos que vimos antes o escribir el archivo .yml, pero cuando quieras volver a tener la misma configuración de contenedores trabajando, solo con llamar al archivo .yml volverá a crear toda la configuración

Vamos a crear una carpeta donde guardaremos los archivos de Docker Compose

	
< > Input
Python
!mkdir dockerComposeFiles
Copied

Creamos el archivo .yml dentro

	
< > Input
Python
!touch dockerComposeFiles/docker-compose.yml
Copied

Un archivo Docker Compose tiene que empezar por la versión

version: "<v.v>"

En el momento de escribir esto, la última versión es la 3.8 así que escribimos esa

*docker-compose.yml*:

    version: "3.8"

A continuación se indican los servicios, que son los contenedores. En cada servicio hay que especificar la imagen y, además, se pueden añadir otros parámetros como puertos, variables de entorno, etc.

services:
container1:
image: ubuntu

container2:
image: ubuntu

El docker-compose.yml quedaría así:

  version: "3.8"

services:
container1:
image: ubuntu

container2:
image: ubuntu

Una vez que hemos creado el archivo, en su path, podemos ejecutar todo mediante el comando docker compose up, pero además, añadiendo la opción -d, haremos que corra en segundo plano

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 1/0
⠿ Network dockercomposefiles_default Created 0.1s
⠋ Container dockercomposefiles-container2-1 Creating 0.0s
⠋ Container dockercomposefiles-container1-1 Creating 0.0s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠙ Container dockercomposefiles-container2-1 Creating 0.1s
⠙ Container dockercomposefiles-container1-1 Creating 0.1s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.2s
⠿ Container dockercomposefiles-container1-1 Starting 0.2s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.3s
⠿ Container dockercomposefiles-container1-1 Starting 0.3s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.4s
⠿ Container dockercomposefiles-container1-1 Starting 0.4s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.5s
⠿ Container dockercomposefiles-container1-1 Starting 0.5s
[+] Running 2/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Started 0.5s
⠿ Container dockercomposefiles-container1-1 Starting 0.6s
[+] Running 3/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Started 0.5s
⠿ Container dockercomposefiles-container1-1 Started 0.7s

Si nos fijamos, ha creado dos contenedores dockercomposefiles-container1-1 y dockercomposefiles-container2-1 y la red que los une dockercomposefiles_default

Vamos a borrar los dos contenedores

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

Y borramos la red que ha creado

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

Vamos a intentar hacer lo que hicimos antes con lo que sabemos hasta ahora. Creamos una nueva imagen que venga con ping instalada

*Dockerfile*:

    FROM ubuntu:20.04
          RUN apt update
          RUN apt install iputils-ping -y

Y la compilamos

	
< > Input
Python
!docker build -t ubuntu:ping ./dockerImages
Copied
>_ Output
			
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:20.04
---&gt; a0ce5a295b63
Step 2/3 : RUN apt update
---&gt; Running in 3bd5278d39b4
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]
Get:2 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]
Get:3 http://security.ubuntu.com/ubuntu focal-security/universe amd64 Packages [898 kB]
Get:4 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB]
Get:5 http://archive.ubuntu.com/ubuntu focal-backports InRelease [108 kB]
Get:6 http://archive.ubuntu.com/ubuntu focal/universe amd64 Packages [11.3 MB]
Get:7 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages [2133 kB]
Get:8 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 Packages [27.5 kB]
Get:9 http://security.ubuntu.com/ubuntu focal-security/restricted amd64 Packages [1501 kB]
Get:10 http://archive.ubuntu.com/ubuntu focal/main amd64 Packages [1275 kB]
Get:11 http://archive.ubuntu.com/ubuntu focal/restricted amd64 Packages [33.4 kB]
Get:12 http://archive.ubuntu.com/ubuntu focal/multiverse amd64 Packages [177 kB]
Get:13 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages [2594 kB]
Get:14 http://archive.ubuntu.com/ubuntu focal-updates/restricted amd64 Packages [1613 kB]
Get:15 http://archive.ubuntu.com/ubuntu focal-updates/multiverse amd64 Packages [30.2 kB]
Get:16 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 Packages [1200 kB]
Get:17 http://archive.ubuntu.com/ubuntu focal-backports/universe amd64 Packages [27.4 kB]
...
Successfully built c3d32aa9de02
Successfully tagged ubuntu:ping
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Comprobamos que se ha creado

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu ping c3d32aa9de02 About a minute ago 112MB
maximofn/ubuntu test a78cf3ea16d8 25 hours ago 77.8MB
nginx latest 2d389e545974 33 hours ago 142MB
ubuntu latest 2dc39ba059dc 12 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 12 days ago 72.8MB
hello-world latest feb5d9fea6a5 11 months ago 13.3kB

Le cambiamos el tag

	
< > Input
Python
!docker tag ubuntu:ping maximofn/ubuntu:ping
Copied
	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu ping c3d32aa9de02 About a minute ago 112MB
maximofn/ubuntu ping c3d32aa9de02 About a minute ago 112MB
maximofn/ubuntu test c3d32aa9de02 About a minute ago 112MB
nginx latest 2d389e545974 33 hours ago 142MB
ubuntu latest 2dc39ba059dc 12 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 12 days ago 72.8MB
hello-world latest feb5d9fea6a5 11 months ago 13.3kB

Editamos el archivo Docker Compose para que coja las imágenes que acabamos de crear

*docker-compose.yml*:

  version: "3.8"

services:
container1:
image: maximofn/ubuntu:ping

container2:
image: maximofn/ubuntu:ping

Y además le decimos que ejecute una no operación

El docker-compose.yml quedaría así:

  version: "3.8"

services:
container1:
image: ubuntu
command: tail -f /dev/null

container2:
image: ubuntu
command: tail -f /dev/null

Lo levantamos

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container1-1 Recreate 0.1s
⠋ Container dockercomposefiles-container2-1 Recreate 0.1s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠙ Container dockercomposefiles-container2-1 Recreate 0.2s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠹ Container dockercomposefiles-container2-1 Recreate 0.3s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠸ Container dockercomposefiles-container2-1 Recreate 0.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠼ Container dockercomposefiles-container2-1 Recreate 0.5s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠴ Container dockercomposefiles-container2-1 Recreate 0.6s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
...
⠸ Container dockercomposefiles-container2-1 Recreate 1.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
...
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 10.8s
⠿ Container dockercomposefiles-container2-1 Started 10.9s
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 10.8s
⠿ Container dockercomposefiles-container2-1 Started 10.9s

Vemos los contenedores que están corriendo

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
935939e5a75d maximofn/ubuntu:ping "tail -f /dev/null" 15 seconds ago Up 13 seconds dockercomposefiles-container2-1
f9138d7064dd maximofn/ubuntu:ping "tail -f /dev/null" 25 seconds ago Up 13 seconds dockercomposefiles-container1-1

Están los dos contenedores corriendo, ahora nos metemos en uno e intentamos hacer ping al otro

$ docker exec -it dockercomposefiles-container1-1 bash
root@f9138d7064dd:/# ping dockercomposefiles-container2-1
PING dockercomposefiles-container2-1 (172.21.0.3) 56(84) bytes of data.
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=1 ttl=64 time=0.110 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=3 ttl=64 time=0.049 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=4 ttl=64 time=0.075 ms
^C
--- dockercomposefiles-container2-1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3068ms
rtt min/avg/max/mdev = 0.049/0.070/0.110/0.025 ms

Como vemos, podemos hacer ping, hemos creado bien la imagen con ping instalado. Además, en el docker-compose hemos hecho que se ejecute una no operación para que los contenedores estén corriendo

Borramos los dos contenedores y la red que hemos creado

	
< > Input
Python
!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1
Copied
>_ Output
			
dockercomposefiles-container1-1
dockercomposefiles-container2-1
	
< > Input
Python
!docker network rm dockercomposefiles_default
Copied
>_ Output
			
dockercomposefiles_default

Cómo nombra Docker Compose los contenedoreslink image 4

Si nos fijamos, los contenedores que crea Docker se llaman dockercomposefiles-container1-1 y dockercomposefiles-container2-1. Esto es porque la carpeta en la que está el archivo de Docker Compose está en una carpeta llamada dockerComposeFiles, por eso la primera parte del nombre de los contenedores es dockercomposefiles, a continuación aparece el nombre del servicio que le hemos dado en el archivo Docker Compose (container1 y container2) y por último un número para poder crear más si es necesario

Similarmente ocurre con el nombre de la red que ha creado dockercomposefiles_default

Logs en docker composelink image 5

Vamos ahora a cambiar el archivo Docker Compose, en las líneas en las que teníamos command: tail -f /dev/null, vamos a poner command: ping 0.0.0.0

Y además le decimos que ejecute una no operación

El docker-compose.yml quedaría así:

  version: "3.8"

services:
container1:
image: ubuntu
command: ping 0.0.0.0

container2:
image: ubuntu
command: ping 0.0.0.0

Esto lo hacemos para que cada contenedor esté escupiendo el ping constantemente, así simulamos algunos logs

Si ejecutamos de nuevo el docker compose

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container1-1 Recreate 0.1s
⠋ Container dockercomposefiles-container2-1 Recreate 0.1s
[+] Running 0/2
⠙ Container dockercomposefiles-container1-1 Recreate 0.2s
⠙ Container dockercomposefiles-container2-1 Recreate 0.2s
[+] Running 0/2
⠹ Container dockercomposefiles-container1-1 Recreate 0.3s
⠹ Container dockercomposefiles-container2-1 Recreate 0.3s
[+] Running 0/2
⠸ Container dockercomposefiles-container1-1 Recreate 0.4s
⠸ Container dockercomposefiles-container2-1 Recreate 0.4s
[+] Running 0/2
⠼ Container dockercomposefiles-container1-1 Recreate 0.5s
⠼ Container dockercomposefiles-container2-1 Recreate 0.5s
[+] Running 0/2
⠴ Container dockercomposefiles-container1-1 Recreate 0.6s
⠴ Container dockercomposefiles-container2-1 Recreate 0.6s
[+] Running 0/2
⠦ Container dockercomposefiles-container1-1 Recreate 0.7s
⠦ Container dockercomposefiles-container2-1 Recreate 0.7s
[+] Running 0/2
⠧ Container dockercomposefiles-container1-1 Recreate 0.8s
⠧ Container dockercomposefiles-container2-1 Recreate 0.8s
[+] Running 0/2
...
⠿ Container dockercomposefiles-container1-1 Starting 11.0s
⠿ Container dockercomposefiles-container2-1 Started 11.0s
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 11.1s
⠿ Container dockercomposefiles-container2-1 Started 11.0s

Ahora podemos ver los logs de los dos contenedores mediante el comando docker compose logs

	
< > Input
Python
!cd dockerComposeFiles && docker compose logs
Copied
>_ Output
			
dockercomposefiles-container2-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 ms
dockercomposefiles-container1-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
...
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=214 ttl=64 time=0.015 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=215 ttl=64 time=0.021 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=216 ttl=64 time=0.020 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=217 ttl=64 time=0.049 ms

Como vemos, podemos ver los logs de los dos contenedores, pero en el caso de querer ver solo los de uno, podemos especificar el **nombre del servicio**

	
< > Input
Python
!cd dockerComposeFiles && docker compose logs container1
Copied
>_ Output
			
dockercomposefiles-container1-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.023 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.031 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.033 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.022 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.032 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.029 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.031 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.024 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.029 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.032 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.033 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.028 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.034 ms
...
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=332 ttl=64 time=0.027 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=333 ttl=64 time=0.030 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=334 ttl=64 time=0.033 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=335 ttl=64 time=0.036 ms
	
< > Input
Python
!cd dockerComposeFiles && docker compose logs container2
Copied
>_ Output
			
dockercomposefiles-container2-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=23 ttl=64 time=0.035 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=24 ttl=64 time=0.037 ms
...
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=340 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=341 ttl=64 time=0.033 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=342 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=343 ttl=64 time=0.036 ms

Si queremos ver los logs continuamente, podemos añadir la opción -f: docker compose logs -f <service name>

Si he hecho un docker compose con más de dos servicios, cuando se quiera ver los logs de varios servicios solo hay que añadir más nombres al comando, docker compose logs <name service 1> <name service 2> ...

Exec servicioslink image 6

Como hemos visto, mediante el comando exec podemos entrar a un contenedor indicando el nombre del contenedor, el comando que se quiere ejecutar y la opción -it. Con Docker Compose esto es más sencillo, ya que solamente es necesario el nombre del servicio y el comando, pero no es necesaria la opción -it ya que Docker Compose lo da por sentado

$ docker compose exec container1 bash
root@a7cf282fe66c:/#

Deteniendo docker composelink image 7

Cuando hemos terminado de trabajar, con un solo comando (stop), Docker Compose para todo, no hace falta ir parando uno a uno cada contenedor

	
< > Input
Python
!cd dockerComposeFiles && docker compose stop
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container2-1 Stopping 0.1s
⠋ Container dockercomposefiles-container1-1 Stopping 0.1s
[+] Running 0/2
⠙ Container dockercomposefiles-container2-1 Stopping 0.2s
⠙ Container dockercomposefiles-container1-1 Stopping 0.2s
[+] Running 0/2
⠹ Container dockercomposefiles-container2-1 Stopping 0.3s
⠹ Container dockercomposefiles-container1-1 Stopping 0.3s
[+] Running 0/2
⠸ Container dockercomposefiles-container2-1 Stopping 0.4s
⠸ Container dockercomposefiles-container1-1 Stopping 0.4s
[+] Running 0/2
⠼ Container dockercomposefiles-container2-1 Stopping 0.5s
⠼ Container dockercomposefiles-container1-1 Stopping 0.5s
[+] Running 0/2
⠴ Container dockercomposefiles-container2-1 Stopping 0.6s
⠴ Container dockercomposefiles-container1-1 Stopping 0.6s
[+] Running 0/2
⠦ Container dockercomposefiles-container2-1 Stopping 0.7s
⠦ Container dockercomposefiles-container1-1 Stopping 0.7s
[+] Running 0/2
⠧ Container dockercomposefiles-container2-1 Stopping 0.8s
⠧ Container dockercomposefiles-container1-1 Stopping 0.8s
...
[+] Running 1/2
⠿ Container dockercomposefiles-container2-1 Stopped 10.4s
⠸ Container dockercomposefiles-container1-1 Stopping 10.4s
[+] Running 2/2
⠿ Container dockercomposefiles-container2-1 Stopped 10.4s
⠿ Container dockercomposefiles-container1-1 Stopped 10.4s

Como se puede ver, docker compose ha parado los dos contenedores, pero no los ha borrado, ni ha borrado la red

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e6c1dd9adb2 maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container2-1
a7cf282fe66c maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container1-1
	
< > Input
Python
!docker network ls
Copied
>_ Output
			
NETWORK ID NAME DRIVER SCOPE
13cc632147f3 bridge bridge local
d4a2f718cd83 dockercomposefiles_default bridge local
da1f5f6fccc0 host host local
d3b0d93993c0 none null local

Docker compose como herramienta de desarrollolink image 8

Al igual que vimos antes, para poder desarrollar, lo ideal sería compartir la carpeta que tiene el código con el servicio. Esto con docker compose se hace añadiendo la etiqueta volumes al archivo docker compose. Primero tenemos que añadirle la ruta de la carpeta donde está el código en el host y luego la ruta en el contenedor.

*docker-compose.yml*:

  version: "3.8"

services:
container1:
image: ubuntu
command: ping 0.0.0.0
volumes:
- ../dockerHostFolder/:/dockerContainerFolder

container2:
image: ubuntu
command: ping 0.0.0.0

Cómo se puede ver, la ruta de la carpeta del host la he puesto relativa

Si levantamos el Docker Compose

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 1/0
⠋ Container dockercomposefiles-container1-1 Recreate 0.1s
⠿ Container dockercomposefiles-container2-1 Created 0.0s
[+] Running 0/2
⠿ Container dockercomposefiles-container1-1 Starting 0.2s
⠿ Container dockercomposefiles-container2-1 Starting 0.2s
[+] Running 0/2
⠿ Container dockercomposefiles-container1-1 Starting 0.3s
⠿ Container dockercomposefiles-container2-1 Starting 0.3s
[+] Running 0/2
⠿ Container dockercomposefiles-container1-1 Starting 0.4s
⠿ Container dockercomposefiles-container2-1 Starting 0.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Started 0.5s
⠿ Container dockercomposefiles-container2-1 Starting 0.5s
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 0.5s
⠿ Container dockercomposefiles-container2-1 Started 0.6s

Si entramos en el contenedor, podemos ver qué hay dentro del archivo text.txt

$ docker compose exec container1 bash
root@c8aae9d619d3:/# ls dockerContainerFolder/
bindFile.txt fileExtract.txt text.txt
root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
hola contenedor

Si ahora lo abrimos en el host, escribimos hola host y volvemos a ver en el contenedor

root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt 
hola host

Y ahora al revés, si lo modificamos en el contenedor

root@c8aae9d619d3:/# echo hola compose > dockerContainerFolder/text.txt
root@c8aae9d619d3:/# exit
exit

Si lo vemos desde el host, debemos obtener hola compose

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

Exposición de puertos en docker composelink image 9

También podemos configurar los puertos en el archivo de Docker Compose, mediante la etiqueta ports, indicando el puerto del host y a continuación la IP del servicio

ports:
- <host port>:<service port>

Docker compose en equipo - docker overridelink image 10

Si somos un grupo de personas desarrollando sobre Docker con Docker Compose, es probable que muchas personas anden cambiando el archivo Docker Compose, lo cual puede hacer que no se sincronicen bien y haya conflictos.

Para solucionar esto Docker ofrece una herramienta llamada Docker Override. De esta manera puede haber un archivo Docker Compose base y que cada uno lo modifique mediante Docker Override.

Para hacer esto, ahora tenemos que crear un archivo llamado docker-compose.override.yml que será el que podremos editar

	
< > Input
Python
!touch dockerComposeFiles/docker-compose.override.yml
Copied

Si ahora intentamos levantar el Docker Compose, vamos a recibir un error

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
Top-level object must be a mapping

Y esto es porque Docker Compose ha detectado que hay un archivo llamado docker-compose.override.yml y que está vacío, por lo que vamos a editarlo. El archivo docker-compose.override.yml lo que hace es editar el archivo docker-compose.yml, por lo que si por ejemplo queremos hacer un cambio en el servicio container2 para añadirle un volumen escribiríamos así el archivo docker-compose.override.yml

*docker-compose.override.yml*:

  version: "3.8"

services:
container2:
volumes:
- ../dockerHostFolder/:/dockerOverrideFolder

Date cuenta de que la carpeta compartida en el servicio la he llamado dockerOverrideFolder, por lo que vamos a levantar el docker compose y ver si vemos esa carpeta en el contenedor container2

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 1/0
⠋ Container dockercomposefiles-container2-1 Recreate 0.1s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠙ Container dockercomposefiles-container2-1 Recreate 0.2s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠹ Container dockercomposefiles-container2-1 Recreate 0.3s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠸ Container dockercomposefiles-container2-1 Recreate 0.4s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠼ Container dockercomposefiles-container2-1 Recreate 0.5s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠴ Container dockercomposefiles-container2-1 Recreate 0.6s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠦ Container dockercomposefiles-container2-1 Recreate 0.7s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠧ Container dockercomposefiles-container2-1 Recreate 0.8s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
...
[+] Running 1/2
⠿ Container dockercomposefiles-container2-1 Starting 10.8s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 2/2
⠿ Container dockercomposefiles-container2-1 Started 10.8s
⠿ Container dockercomposefiles-container1-1 Running 0.0s

Vemos que ha tardado 10 segundos en montar el servicio container2, eso es porque ha estado aplicando los cambios

$ docker compose exec container2 bash
root@d8777a4e611a:/# ls dockerOverrideFolder/
bindFile.txt fileExtract.txt text.txt
root@d8777a4e611a:/# cat dockerOverrideFolder/text.txt
hola compose
root@d8777a4e611a:/# exit

Bajamos el Compose y borramos los contenedores y la red creada

	
< > Input
Python
!cd dockerComposeFiles && docker compose down
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container2-1 Stopping 0.1s
⠋ Container dockercomposefiles-container1-1 Stopping 0.1s
[+] Running 0/2
⠙ Container dockercomposefiles-container2-1 Stopping 0.2s
⠙ Container dockercomposefiles-container1-1 Stopping 0.2s
[+] Running 0/2
⠹ Container dockercomposefiles-container2-1 Stopping 0.3s
⠹ Container dockercomposefiles-container1-1 Stopping 0.3s
[+] Running 0/2
⠸ Container dockercomposefiles-container2-1 Stopping 0.4s
⠸ Container dockercomposefiles-container1-1 Stopping 0.4s
[+] Running 0/2
⠼ Container dockercomposefiles-container2-1 Stopping 0.5s
⠼ Container dockercomposefiles-container1-1 Stopping 0.5s
[+] Running 0/2
⠴ Container dockercomposefiles-container2-1 Stopping 0.6s
⠴ Container dockercomposefiles-container1-1 Stopping 0.6s
[+] Running 0/2
⠦ Container dockercomposefiles-container2-1 Stopping 0.7s
⠦ Container dockercomposefiles-container1-1 Stopping 0.7s
[+] Running 0/2
⠧ Container dockercomposefiles-container2-1 Stopping 0.8s
⠧ Container dockercomposefiles-container1-1 Stopping 0.8s
...
⠸ Container dockercomposefiles-container2-1 Stopping 10.4s
⠸ Container dockercomposefiles-container1-1 Stopping 10.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container2-1 Removed 10.4s
⠿ Container dockercomposefiles-container1-1 Removing 10.5s
[+] Running 2/2
⠿ Container dockercomposefiles-container2-1 Removed 10.4s
⠿ Container dockercomposefiles-container1-1 Removed 10.5s
⠋ Network dockercomposefiles_default Removing 0.1s
[+] Running 3/3
⠿ Container dockercomposefiles-container2-1 Removed 10.4s
⠿ Container dockercomposefiles-container1-1 Removed 10.5s
⠿ Network dockercomposefiles_default Removed 0.2s

En este caso, solo con down Docker Compose ha parado y borrado todo, ya que, como vemos en los contenedores y en la red pone Removed

Docker compose restartlink image 11

A la hora de escribir un docker compose, podemos añadir la etiqueta restart para que si el contenedor se cae, se reinicie automáticamente

restart: always

De esta manera, si el contenedor se cae, se reiniciará automáticamente. Si queremos que se reinicie solo un número de veces, podemos añadirle la opción on-failure

restart: on-failure:<number>

Ahora el contenedor se reiniciará un número de veces, pero si se cae más veces, no se reiniciará. Si queremos que se reinicie siempre, podemos añadirle la opción unless-stopped

restart: unless-stopped

Ahora el contenedor se reiniciará siempre, a menos que se pare manualmente

Docker avanzadolink image 12

Administrar ambiente de trabajolink image 13

Borrado de contenedores apagadoslink image 14

Después de estar un tiempo desarrollando, podemos tener varios contenedores apagados, pero guardados en el ordenador. Esto al final ocupa memoria, así que con docker container prune podemos eliminar todos los que están parados

	
< > Input
Python
!docker run ubuntu
Copied
	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
effcee24f54a ubuntu "bash" 37 seconds ago Exited (0) 36 seconds ago musing_rosalind
$ docker container prune 
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
effcee24f54aab22e34fdea2465b3b7af132d8c627e5432ba3e915a370876977

Total reclaimed space: 0B

En este caso hemos ahorrado 0 bytes, pero en el caso de dejar contenedores apagados después de mucho desarrollo, seguro que el ahorro de memoria será mayor

Borrado de todos los contenedoreslink image 15

En caso de tener contenedores corriendo, podemos eliminar todos los contenedores mediante otro comando

El comando docker ps -q nos devuelve la ID de todos los contenedores, por lo que con el comando docker rm -f $(docker ps -aq) pararemos y borraremos todos

	
< > Input
Python
!docker run -d ubuntu tail -f /dev/null
Copied
>_ Output
			
c22516186ef7e3561fb1ad0d508a914857dbc61274a218f297c4d80b1fc33863
	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c22516186ef7 ubuntu "tail -f /dev/null" About a minute ago Up About a minute agitated_knuth
	
< > Input
Python
!docker rm -f $(docker ps -aq)
Copied
>_ Output
			
c22516186ef7
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Borrado de todolink image 16

Como hemos visto Docker también crea redes, imágenes, volúmenes, etc, así que con el comando docker system prune podemos borrar todos los contenedores parados, todas las redes que no estén usadas por al menos un contenedor, las imágenes repetidas y lo que haya repetido en la caché de compilación

$ docker system prune 
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all dangling build cache

Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

Al igual que antes, no se ha ahorrado mucho espacio, pero después de mucho tiempo desarrollando, el ahorro será considerable

Uso de recursos del host por parte de contenedoreslink image 17

Por ejemplo, a la hora de crear un contenedor, podemos limitar la RAM que el host puede usar mediante la opción --memory

	
< > Input
Python
!docker run -d --memory 1g ubuntu tail -f /dev/null
Copied
>_ Output
			
d84888eafe531831ef8915d2270422365adec02678122bf59580e2da782e6972

Pero con docker ps no tenemos acceso a los recursos que está consumiendo el contenedor

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d84888eafe53 ubuntu "tail -f /dev/null" 35 seconds ago Up 34 seconds musing_ritchie

Para ello tenemos el comando docker stats

$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d84888eafe53 musing_ritchie 0.00% 540KiB / 1GiB 0.05% 5.62kB / 0B 0B / 0B 1

Esto es muy útil si queremos simular un entorno con un límite de RAM

Deteniendo contenedores correctamente: SHELL vs EXEClink image 18

Como hemos explicado, cuando asignamos un proceso a un contenedor, cuando ese proceso termina, el contenedor se para, pero a veces podemos encontrarnos con problemas con esto. Vamos a crear una nueva carpeta llamada Dockerfile_loop

	
< > Input
Python
!mkdir Dockerfile_loop
Copied

Ahora vamos a crear un archivo llamado loop.sh dentro de Dockerfile_loop

	
< > Input
Python
!touch Dockerfile_loop/loop.sh
Copied

Y vamos a escribir lo siguiente dentro de loop.sh

#!/usr/bin/env bash
      trap "exit 0" SIGTERM
      while true; do :; done

Si yo ejecuto este script en el host se ejecuta hasta que introduzca CTRL+C

``` bash

./loop

^C

```

Ahora vamos a crear un archivo Dockerfile dentro de Dockerfile_loop

	
< > Input
Python
!touch Dockerfile_loop/Dockerfile
Copied

*Dockerfile*:

FROM ubuntu:trusty
      COPY ["loop.sh", "/"]
      CMD /loop.sh

Vamos a crear una imagen basada en Ubuntu que copia el script dentro y lo ejecuta, y el script se ejecuta hasta que recibe la señal SIGTERM del sistema operativo. Compilamos la imagen

	
< > Input
Python
!docker build -t ubuntu:loop ./Dockerfile_loop
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/3 : COPY ["loop.sh", "/"]
---&gt; 89f2bbd25a88
Step 3/3 : CMD /loop.sh
---&gt; Running in ff52569c35fd
Removing intermediate container ff52569c35fd
---&gt; feb091e4efa3
Successfully built feb091e4efa3
Successfully tagged ubuntu:loop
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Ejecutamos el contenedor

docker run -d --name looper ubuntu:loop bash
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
8a28f8cc9892213c4e0603dfdde320edf52c091b82c60510083549a391cd6645

Comprobamos y vemos que el contenedor está corriendo

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 4 seconds ago Up 3 seconds looper

Intentamos parar el contenedor con docker stop looper. Docker stop trata de parar el contenedor enviándole la señal SIGTERM.

	
< > Input
Python
%%time
!docker stop looper
Copied
>_ Output
			
looper
CPU times: user 89.2 ms, sys: 21.7 ms, total: 111 ms
Wall time: 10.6 s

Esto ha tardado unos 10 segundos en detenerse, cuando tendría que ser inmediato. Esto es porque stop ha mandado la orden SIGTERM para que se parara el contenedor, pero como no paraba, al rato le ha mandado un SIGKILL para forzar que se detenga. Veamos a ver qué pasa, si listamos los contenedores

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 23 seconds ago Exited (137) 2 seconds ago looper

Podemos ver que la señal de Exited es 137, eso equivale a SIGKILL, es decir, Docker tuvo que forzar el apagado.

Vamos a borrar el contenedor y a volver a ejecutarlo

	
< > Input
Python
!docker rm looper
Copied
>_ Output
			
looper
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
84bc37f944d270be5f84a952968db2b8cf5372c61146d29383468198ceed18fd

Si ahora intentamos detener el contenedor con docker kill looper

	
< > Input
Python
%%time
!docker kill looper
Copied
>_ Output
			
looper
CPU times: user 9.1 ms, sys: 857 µs, total: 9.96 ms
Wall time: 545 ms

Vemos que el tiempo son unos 500 ms, es decir, docker lo ha parado en un momento enviándole la orden SIGKILL. Porque kill no manda SIGTERM y si en un tiempo no se ha parado el contenedor manda SIGKILL, lo que hace es mandar SIGKILL desde el inicio.

Si vemos los contenedores, vemos que la señal de salida es la misma, 137

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
84bc37f944d2 ubuntu:loop "/bin/sh -c /loop.sh" 6 seconds ago Exited (137) 2 seconds ago looper

Esta no es la manera correcta de apagar un contenedor, porque cuando queramos apagar el contenedor habría que hacerlo mediante la señal SIGTERM, para que este termine de procesar lo que estuviera haciendo y luego se apague

Si borramos el contenedor y lo volvemos a ejecutar

	
< > Input
Python
!docker rm looper
Copied
>_ Output
			
looper
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
b9d9f370cc0de7569eb09d0a85cd67e8ea6babc0754a517ccba5c5057f5cc50e

Si ahora vemos los procesos que se están ejecutando dentro del contenedor

	
< > Input
Python
!docker exec looper ps -ef
Copied
>_ Output
			
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:05 ? 00:00:00 /bin/sh -c /loop.sh
root 7 1 93 14:05 ? 00:00:02 bash /loop.sh
root 8 0 0 14:05 ? 00:00:00 ps -ef

En realidad el proceso principal, el 1, no es /loop.sh sino que es /bin/sh -c /loop.sh, es decir, es un proceso hijo del shell. Por lo que cuando llegaba la señal SIGTERM le llegaba al shell, pero este no se la manda a sus procesos hijos, por eso no le llegaba a loop.sh

Para que no pase esto hay que cambiar el Dockerfile a lo siguiente

*Dockerfile*:

FROM ubuntu:trusty
      COPY ["loop.sh", "/"]
      CMD ["/loop.sh"]    # antes era CMD /loop.sh

Esta forma se llama exec form, mientras que la anterior se llama shell form, de manera que de la anterior forma corre el proceso como un hijo del shell, mientras que de la forma exec form ejecuta el proceso que le digamos. Así que borramos el contenedor, volvemos a compilar y volvemos a hacer correr el contenedor con la imagen

	
< > Input
Python
!docker rm -f looper
Copied
>_ Output
			
looper
	
< > Input
Python
!docker build -t ubuntu:loop ./Dockerfile_loop
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/3 : COPY ["loop.sh", "/"]
---&gt; Using cache
---&gt; 89f2bbd25a88
Step 3/3 : CMD ["/loop.sh"]
---&gt; Running in 6b8d92fcd57c
Removing intermediate container 6b8d92fcd57c
---&gt; 35a7bb2b1892
Successfully built 35a7bb2b1892
Successfully tagged ubuntu:loop
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
850ae70c071426850b28428ac60dcbf875c6d35d9b7cc66c17cf391a23392965

Sí, ahora veo los procesos dentro del contenedor

	
< > Input
Python
!docker exec looper ps -ef
Copied
>_ Output
			
UID PID PPID C STIME TTY TIME CMD
root 1 0 88 14:14 ? 00:00:02 bash /loop.sh
root 7 0 0 14:14 ? 00:00:00 ps -ef

Ahora sí veo que el proceso principal, el 1, es /loop.sh

Si ahora pruebo a parar el contenedor

	
< > Input
Python
%%time
!docker stop looper
Copied
>_ Output
			
looper
CPU times: user 989 µs, sys: 7.55 ms, total: 8.54 ms
Wall time: 529 ms

Vemos que tarda más. Veamos el código con el que paró

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
850ae70c0714 ubuntu:loop "/loop.sh" About a minute ago Exited (0) 33 seconds ago looper

Contenedores ejecutableslink image 19

Si queremos un binario que corra como un ejecutable, en el dockerfile hay que especificar el comando en ENTRYPOINT y los parámetros del comando en CMD, vamos a verlo

Vamos a crear una nueva carpeta donde guardaremos el Dockerfile

	
< > Input
Python
!mkdir dockerfile_ping
Copied

Ahora creamos un Dockerfile dentro

	
< > Input
Python
!touch dockerfile_ping/Dockerfile
Copied

Escribimos dentro del Dockerfile lo siguiente

FROM ubuntu:trusty
      ENTRYPOINT [ "/bin/ping", "-c", "3" ]
      CMD [ "localhost" ]

Compilamos la imagen

	
< > Input
Python
!docker build -t ubuntu:ping ./dockerfile_ping
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/3 : ENTRYPOINT [ "/bin/ping", "-c", "3" ]
---&gt; Using cache
---&gt; 1cebcfb542b1
Step 3/3 : CMD [ "localhost" ]
---&gt; Using cache
---&gt; 04ddc3de52a2
Successfully built 04ddc3de52a2
Successfully tagged ubuntu:ping
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Si ahora corremos la imagen sin pasarle un parámetro, el contenedor se hará un ping a sí mismo

	
< > Input
Python
!docker run --name ping_localhost ubuntu:ping
Copied
>_ Output
			
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.054 ms
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2027ms
rtt min/avg/max/mdev = 0.041/0.051/0.058/0.007 ms

Pero si ahora le pasamos un parámetro, hará ping a la dirección que le digamos

	
< > Input
Python
!docker run --name ping_google ubuntu:ping google.com
Copied
>_ Output
			
PING google.com (216.58.209.78) 56(84) bytes of data.
64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=1 ttl=111 time=3.93 ms
64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=2 ttl=111 time=6.80 ms
64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=3 ttl=111 time=6.92 ms
--- google.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 3.930/5.886/6.920/1.383 ms

Borramos los contenedores

	
< > Input
Python
!docker rm ping_localhost ping_google
Copied
>_ Output
			
ping_localhost
ping_google

El contexto de buildlink image 20

Vamos a crear una carpeta llamada dockerfile_contexto

	
< > Input
Python
!mkdir dokerfile_contexto
Copied

Ahora creamos en ella dos archivos: un test.txt y el Dockerfile

	
< > Input
Python
!touch dokerfile_contexto/Dockerfile dokerfile_contexto/text.txt
Copied

Modificamos el Dockerfile y ponemos lo siguiente

FROM ubuntu:trusty
      COPY [".", "/"]

Esto lo que va a hacer es que va a copiar dentro de la imagen todo lo que tenga en la carpeta en la que se encuentra el Dockerfile. Compilamos la imagen

	
< > Input
Python
!docker build -t ubuntu:contexto ./dokerfile_contexto
Copied
>_ Output
			
Sending build context to Docker daemon 2.56kB
Step 1/2 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/2 : COPY [".", "/"]
---&gt; 3ab79fdce389
Successfully built 3ab79fdce389
Successfully tagged ubuntu:contexto
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Vamos a ver qué hay dentro del contenedor

	
< > Input
Python
!docker run --name ls ubuntu:contexto ls
Copied
>_ Output
			
Dockerfile
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
text.txt
tmp
usr
var

Como vemos, está el archivo text.txt. Pero es posible que dentro de la carpeta que está en el mismo directorio que el Dockerfile haya archivos o carpetas que no queremos que se copien en la imagen, por la razón que sea, así que, al igual que en git tenemos el .gitignore, en docker tenemos el .dockerignore, donde metemos los archivos o carpetas que no queremos que, a la hora de compilar, se tengan en cuenta

Así que creamos un archivo .dockerignore

	
< > Input
Python
!touch dokerfile_contexto/.dockerignore
Copied

Y dentro añadimos el text.txt, y de paso el Dockerfile, que no lo necesitamos dentro de la imagen

*.dockerignore*:

Dockerfile
      text.txt

Borramos el contenedor que habíamos creado, volvemos a compilar y vemos qué hay dentro del contenedor

	
< > Input
Python
!docker rm ls
Copied
>_ Output
			
ls
	
< > Input
Python
!docker build -t ubuntu:contexto ./dokerfile_contexto
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/2 : COPY [".", "/"]
---&gt; 7a6689546da4
Successfully built 7a6689546da4
Successfully tagged ubuntu:contexto
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
	
< > Input
Python
!docker run --name ls ubuntu:contexto ls
Copied
>_ Output
			
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

Vemos que ahora no están ni Dockerfile ni text.txt. Borramos el contenedor

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

Multi-stage buildlink image 21

Al final de un desarrollo no queremos que todo el código esté en la imagen que se va a mandar a producción.

Podemos dividir el dockerfile en dos, por ejemplo, el developer.Dockerfile y el production.Dockerfile, donde en desarrollo habrá más cosas que en el de producción. A la hora de compilarlos, mediante la opción -f elegimos el dockerfile que queremos usar

docker build -t <tag> -f developer.Dockerfile
docker build -t <tag> -f production.Dockerfile

Pero para no tener que crear dos archivos Dockerfile, Docker creó el multi-stage builds. Con un solo Dockerfile vamos a solucionar el problema

Creamos la carpeta donde vamos a guardar el Dockerfile

	
< > Input
Python
!mkdir docker_multi_stage
Copied

Y dentro creamos el archivo Dockerfile

	
< > Input
Python
!cd docker_multi_stage && touch Dockerfile
Copied

Editamos el archivo, metiendo lo siguiente

# Etapa 1: Generar el ejecutable con Python basado en Alpine
FROM python:3.9-alpine as build-stage
WORKDIR /app
# Instalar dependencias para PyInstaller
RUN apk add --no-cache gcc musl-dev libc-dev
# Generar hello.py
RUN echo 'print("Hello from Alpine!")' > hello.py
# Instalar PyInstaller
RUN pip install pyinstaller
# Usar PyInstaller para crear un ejecutable independiente
RUN pyinstaller --onefile hello.py

# Etapa 2: Ejecutar el ejecutable en una imagen de Alpine
FROM alpine:latest
WORKDIR /app
# Copiar el ejecutable desde la etapa de build
COPY --from=build-stage /app/dist/hello .
# Comando por defecto para ejecutar el ejecutable
CMD ["./hello"]

Como se puede ver, el Dockerfile está dividido en dos. Por un lado, se trabaja sobre la imagen python:3.9-alpine, que se llama build-stage. Y, por otro lado, trabajamos sobre la imagen alpine:latest, que es una imagen de Linux muy ligera y se utiliza mucho en producción

Lo compilamos

	
< > Input
Python
!docker build -t maximofn/multistagebuild:latest ./docker_multi_stage
Copied
>_ Output
			
[+] Building 0.0s (0/2) docker:default
>_ Output
			
[+] Building 0.2s (4/6) docker:default
=&gt; [internal] load build definition from Dockerfile 0.0s
=&gt; =&gt; transferring dockerfile: 722B 0.0s
=&gt; [internal] load .dockerignore 0.0s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:latest 0.1s
=&gt; [internal] load metadata for docker.io/library/python:3.9-alpine 0.1s
...
=&gt; CACHED [stage-1 3/3] COPY --from=build-stage /app/dist/hello . 0.0s
=&gt; exporting to image 0.0s
=&gt; =&gt; exporting layers 0.0s
=&gt; =&gt; writing image sha256:7fb090d1495d00e892118b6bc3c03400b63a435fd4703 0.0s
=&gt; =&gt; naming to docker.io/maximofn/multistagebuild:latest 0.0s

Si ahora vemos las imágenes que tenemos

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
maximofn/multistagebuild latest 7fb090d1495d 8 minutes ago 13.6MB

Vamos a bajarnos la imagen de Python para ver cuánto pesa

	
< > Input
Python
!docker pull python:3.9-alpine
Copied
>_ Output
			
3.9-alpine: Pulling from library/python
a8db6415: Already exists
d5e70e42: Already exists
3fe96417: Already exists
aa4dddbb: Already exists
518be9f7: Already exists Digest: sha256:6e508b43604ff9a81907ec17405c9ad5c13664e45a5affa2206af128818c7486
Status: Downloaded newer image for python:3.9-alpine
docker.io/library/python:3.9-alpine
	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
maximofn/multistagebuild latest 7fb090d1495d 9 minutes ago 13.6MB
python 3.9-alpine 6946662f018b 9 days ago 47.8MB

Podemos ver que mientras nuestra imagen pesa solo 13.6 MB, la de Python con la que ha construido la aplicación pesa 47.8 MB. Por lo que podemos sacar dos conclusiones: con la primera imagen, la de Python, ha construido la aplicación, ha generado el ejecutable y ese ejecutable es el que usamos en la segunda imagen, la de Alpine. Además podemos ver que aunque la primera imagen que usa es la de Python no se descarga en nuestro sistema, ya que la hemos tenido que descargar nosotros

Pues ya sólo queda probarlo

	
< > Input
Python
!docker run --rm --name multi_stage_build maximofn/multistagebuild
Copied
>_ Output
			
Hello from Alpine!

Funciona!

Multi arch buildslink image 22

Supongamos que queremos hacer una imagen que se pueda ejecutar en un ordenador y en una Raspberry. El ordenador probablemente tenga un micro con arquitectura AMD64, mientras que la Raspberry tiene un micro con arquitectura ARM. Por lo tanto, no podemos crear la misma imagen para los dos. Es decir, cuando creamos una imagen, la creamos con un Dockerfile que suele empezar así

FROM ...

Por lo tanto, el Dockerfile de la imagen del ordenador podría empezar así

FROM ubuntu:latest

Mientras que el de la Raspberry podría empezar así

FROM arm64v8/ubuntu:latest

Tendríamos que crear dos archivos Dockerfile, compilarlos y en el ordenador usar una imagen y en la Raspberry usar otra

Para evitar tener que ver la arquitectura del ordenador y ver qué imagen tenemos que usar, Docker crea los manifest, que, como su nombre indica, es un manifiesto que indica en función de qué arquitectura de micro tengamos, usa una imagen u otra

Así que vamos a ver cómo hacer esto

En primer lugar, creamos una carpeta donde vamos a crear nuestros archivos Dockerfile

	
< > Input
Python
!mkdir docker_multi_arch
Copied

Ahora creamos los dos Dockerfiles

	
< > Input
Python
!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64
Copied

Escribimos el Dockerfile para AMD64

	
< > Input
Python
!cd docker_multi_arch && echo "FROM ubuntu:20.04" &gt;&gt; Dockerfile_amd64 && echo "CMD echo 'Hello from amd64'" &gt;&gt; Dockerfile_amd64
Copied
	
< > Input
Python
!cd docker_multi_arch && echo "FROM arm64v8/ubuntu:latest" &gt;&gt; Dockerfile_arm && echo "CMD echo 'Hello from ARM'" &gt;&gt; Dockerfile_arm
Copied

Ahora combinamos las dos imágenes

	
< > Input
Python
!cd docker_multi_arch && docker build -t maximofn/multiarch:arm -f Dockerfile_arm .
Copied
>_ Output
			
[+] Building 0.0s (0/1) docker:default
[+] Building 0.2s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.1s
[+] Building 0.3s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.2s
[+] Building 0.5s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.4s
[+] Building 0.6s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.5s
...
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load build definition from Dockerfile_arm 0.0s
=&gt; =&gt; transferring dockerfile: 94B 0.0s
=&gt; [internal] load metadata for docker.io/arm64v8/ubuntu:latest 1.8s
=&gt; [auth] arm64v8/ubuntu:pull token for registry-1.docker.io 0.0s
=&gt; CACHED [1/1] FROM docker.io/arm64v8/ubuntu:latest@sha256:94d12db896d0 0.0s
=&gt; exporting to image 0.0s
=&gt; =&gt; exporting layers 0.0s
=&gt; =&gt; writing image sha256:a9732c1988756dc8e836fd96e5c9512e349c97ea5af46 0.0s
=&gt; =&gt; naming to docker.io/maximofn/multiarch:arm 0.0s

Vamos a ver qué tenemos las dos imágenes compiladas

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
maximofn/multiarch arm a9732c198875 4 weeks ago 69.2MB
maximofn/multiarch amd64 5b612c83025f 6 weeks ago 72.8MB

Vemos que hemos compilado las dos imágenes. Para poder crear un manifest, primero tenemos que subir las imágenes a Docker Hub, así que las subimos

	
< > Input
Python
!docker push maximofn/multiarch:amd64
Copied
>_ Output
			
The push refers to repository [docker.io/maximofn/multiarch]
82bdeb5f: Mounted from library/ubuntu amd64: digest: sha256:30e820f2a11a24ad4d8fb624ae485f7c1bcc299e8cfc72c88adce1acd0447e1d size: 529
	
< > Input
Python
!docker push maximofn/multiarch:arm
Copied
>_ Output
			
The push refers to repository [docker.io/maximofn/multiarch]
>_ Output
			
eda53374: Layer already exists arm: digest: sha256:6ec5a0752d49d3805061314147761bf25b5ff7430ce143adf34b70d4eda15fb8 size: 529

Si me voy a mi Docker Hub puedo ver que mi imagen maximofn/multiarch tiene los tags amd64 y arm

docker_multi_arch_tags

Ahora vamos a crear el manifest basado en estas dos imágenes

	
< > Input
Python
!docker manifest create maximofn/multiarch:latest maximofn/multiarch:amd64 maximofn/multiarch:arm
Copied
>_ Output
			
Created manifest list docker.io/maximofn/multiarch:latest

Una vez creado, tenemos que indicar las arquitecturas de las CPU a las que corresponde cada una

	
< > Input
Python
!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:amd64 --os linux --arch amd64
Copied
	
< > Input
Python
!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:arm64 --os linux --arch arm64
Copied
>_ Output
			
manifest for image maximofn/multiarch:arm64 does not exist in maximofn/multiarch:latest

Una vez creado y anotado, podemos subir el manifest a Docker Hub

	
< > Input
Python
!docker manifest push maximofn/multiarch:latest
Copied
>_ Output
			
sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304

Si ahora vuelvo a mirar los tags que tiene mi imagen maximofn/multiarch, veo también el de latest

!docker_multi_arch_tags_manifest

Ahora, tanto si quiero usar mi imagen desde una máquina con CPU AMD64 o CPU ARM al hacer FROM maximofn/multiarch:latest, docker comprobará la arquitectura de la CPU y bajará el tag amd64 o el tag arm. Vamos a verlo, si desde mi ordenador ejecuto la imagen obtengo

	
< > Input
Python
!docker run maximofn/multiarch:latest
Copied
>_ Output
			
Unable to find image 'maximofn/multiarch:latest' locally
>_ Output
			
latest: Pulling from maximofn/multiarch
Digest: sha256:7cef0de10f7fa2b3b0dca0fbf398d1f48af17a0bbc5b9beca701d7c427c9fd84
Status: Downloaded newer image for maximofn/multiarch:latest
Hello from amd64

Como no la tiene, se la baja

Si ahora me conecto por ssh a una Raspberry Pi y pruebo lo mismo, obtengo

raspberry@raspberrypi:~ $ docker run maximofn/multiarch:latest    Unable to find image 'maximofn/multiarch:latest' locally
latest: Pulling from maximofn/multiarch
Digest: sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304
Status: Downloaded newer image for maximofn/multiarch:latest
Hello from ARM

Aparece Hello from ARM ya que la Raspberry tiene un micro con arquitectura ARM

Como se puede ver, cada máquina se ha bajado la imagen que necesitaba

Escritura correcta de Dockerfiles avanzadolink image 23

Ya vimos la manera de escribir correctamente Dockerfiles, pero hay una cosa más que podemos hacer ahora que conocemos el multi-stage build y es crear un contenedor para crear el ejecutable y otro más pequeño para ejecutarlo

Llegamos a la conclusión de que un buen Dockerfile podría ser este

FROM python:3.9.18-alpine
      WORKDIR /sourceCode/sourceApp
      COPY ./sourceCode/sourceApp .
      CMD ["python3", "app.py"]

Vamos a crear ahora un ejecutable en un contenedor builder y en otro más pequeño lo ejecutamos

FROM python:3.9.18-alpine as builder
WORKDIR /sourceCode/sourceApp
RUN apk add --no-cache gcc musl-dev libc-dev && pip install pyinstaller
COPY ./sourceCode/sourceApp .
RUN pyinstaller --onefile app.py

FROM alpine:3.18.3
WORKDIR /sourceCode/sourceApp
COPY --from=builder /sourceCode/sourceApp/dist/app .
CMD ["./app"]

Creamos el código de Python en la ruta necesaria

	
< > Input
Python
!mkdir multistagebuild/sourceCode
!mkdir multistagebuild/sourceCode/sourceApp
!touch multistagebuild/sourceCode/sourceApp/app.py
!echo 'print("Hello from Alpine!")' &gt; multistagebuild/sourceCode/sourceApp/app.py
Copied

Ahora compilando la imagen

	
< > Input
Python
!docker build -t maximofn/multistagebuild:alpine-3.18.3 ./multistagebuild
Copied
>_ Output
			
[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/1) docker:default
[+] Building 0.2s (3/5) docker:default
=&gt; [internal] load build definition from Dockerfile 0.1s
=&gt; =&gt; transferring dockerfile: 357B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:3.18.3 0.1s
=&gt; [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.1s
=&gt; [auth] library/alpine:pull token for registry-1.docker.io 0.0s
[+] Building 0.3s (3/5) docker:default
=&gt; [internal] load build definition from Dockerfile 0.1s
=&gt; =&gt; transferring dockerfile: 357B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:3.18.3 0.2s
=&gt; [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.2s
=&gt; [auth] library/alpine:pull token for registry-1.docker.io 0.0s
[+] Building 0.5s (4/6) docker:default
=&gt; [internal] load build definition from Dockerfile 0.1s
=&gt; =&gt; transferring dockerfile: 357B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:3.18.3 0.4s
...
=&gt; exporting to image 0.1s
=&gt; =&gt; exporting layers 0.1s
=&gt; =&gt; writing image sha256:8a22819145c6fee17e138e818610ccf46d7e13c786825 0.0s
=&gt; =&gt; naming to docker.io/maximofn/multistagebuild:alpine-3.18.3 0.0s

La ejecutamos

	
< > Input
Python
!docker run --rm --name multi_stage_build maximofn/multistagebuild:alpine-3.18.3
Copied
>_ Output
			
Hello from Alpine!

La imagen maximofn/multistagebuild:alpine-3.18.3 solo pesa 13.6 MB

Diferencia entre RUN, CMD y ENTRYPOINTlink image 24

RUNlink image 25

El comando RUN es el más sencillo, simplemente ejecuta un comando en el momento de la compilación de la imagen. Por ejemplo, si queremos instalar un paquete en la imagen, lo hacemos mediante RUN.

Por tanto, es importante: RUN se ejecuta en el momento de la compilación de la imagen, no cuando se ejecuta el contenedor

CMDlink image 26

El comando CMD es el comando que se ejecuta cuando se ejecuta el contenedor. Por ejemplo, si queremos que el contenedor ejecute un comando cuando se ejecute, lo hacemos mediante CMD. Por ejemplo, si tenemos una aplicación de Python en un contenedor, con CMD podemos indicarle que cuando se ejecute el contenedor ejecute la aplicación de Python.

De esta manera, cuando se levante el contenedor, se ejecutará la aplicación de Python. Es decir, si hacemos docker run <image> se ejecutará la aplicación de Python. Pero CMD nos permite sobreescribir el comando que se ejecuta cuando se levanta el contenedor, por ejemplo, si hacemos docker run <image> bash se ejecutará bash en vez de la aplicación de Python.

ENTRYPOINTlink image 27

El comando ENTRYPOINT es similar al comando CMD, pero con una diferencia: y es que ENTRYPOINT no está pensado para sobreescribirse. Es decir, si tenemos una aplicación de Python en un contenedor, con ENTRYPOINT podemos indicarle que cuando se ejecute el contenedor ejecute la aplicación de Python. Pero si hacemos docker run <image> bash se ejecutará la aplicación de Python, no bash.

Un uso muy común de ENTRYPOINT es cuando queremos que el contenedor sea un ejecutable, por ejemplo, si queremos que el contenedor sea un ejecutable de una versión de Python que no tenemos en nuestro host, porque por ejemplo queremos probar la nueva versión de Python que ha salido, podemos hacer

FROM python:3.9.18-alpine
      ENTRYPOINT ["python3"]

De esta manera, cuando se levante el contenedor, se ejecutará Python. Es decir, si hacemos docker run <image> se ejecutará Python. Pero ENTRYPOINT nos permite sobrescribir el comando que se ejecuta cuando se levanta el contenedor, por ejemplo, si hacemos docker run <image> myapp.py se ejecutará python3 myapp.py dentro del contenedor. Así podemos probar nuestra aplicación de Python en la nueva versión de Python

Cambios en un contenedorlink image 28

Con docker diff podemos ver las diferencias que hay entre el contenedor y la imagen, lo que es lo mismo, la diferencia en el contenedor desde que se creó hasta ahora

Vamos a correr un contenedor y dentro creamos un archivo

	
< > Input
Python
!docker run --rm -it --name ubuntu-20.04 ubuntu:20.04 bash
Copied
>_ Output
			
root@895a19aef124:/# touch file.txt

Ahora podemos ver la diferencia

	
< > Input
Python
!docker diff ubuntu-20.04
Copied
>_ Output
			
C /root
A /root/.bash_history
A /file.txt

A significa que se ha añadido, C significa que se ha cambiado y D significa que se ha borrado

Docker en Dockerlink image 29

Supongamos que tenemos contenedores que necesitan levantar o apagar otros contenedores. Esto se logra de la siguiente manera

Dado que en Linux todo es un archivo y el host se comunica con Docker mediante un socket. Por lo que para Linux, ese socket es un archivo. Así que si le montamos ese socket como un archivo al contenedor, podrá hablar con Docker

Primero vamos a montar un contenedor con Ubuntu

	
< > Input
Python
!docker run -d --name ubuntu ubuntu:latest tail -f /dev/null
Copied
>_ Output
			
144091e4a3325c9068064ff438f8865b40f944af5ce649c7156ca55a3453e423

Vamos a montar el contenedor que va a poder hablar con Docker montando la carpeta /var/run/docker.sock

$ docker run -it --rm --name main -v /var/run/docker.sock:/var/run/docker.sock docker:19.03.12
/ #

Nos hemos metido dentro de un contenedor, y si dentro ejecutamos docker ps

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds main
144091e4a332 ubuntu:latest "tail -f /dev/null" 19 seconds ago Up 18 seconds ubuntu

Como podemos ver, dentro de Docker podemos ver los contenedores del host

Podemos correr un nuevo contenedor

# docker run -d --name ubuntu_from_main ubuntu:latest tail -f /dev/null
362654a72bb0fb047c13968707a6f16b87fed7ce051eb5c1a146b15828589a1a
/ #

Y si volvemos a ver los contenedores

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
362654a72bb0 ubuntu:latest "tail -f /dev/null" 3 seconds ago Up 3 seconds ubuntu_from_main
9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" About a minute ago Up About a minute main
144091e4a332 ubuntu:latest "tail -f /dev/null" 2 minutes ago Up About a minute ubuntu

Pero si ahora ejecutamos una nueva terminal del host, veremos el contenedor creado desde dentro del contenedor

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
362654a72bb0 ubuntu:latest "tail -f /dev/null" About a minute ago Up About a minute ubuntu_from_main
9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes main
144091e4a332 ubuntu:latest "tail -f /dev/null" 3 minutes ago Up 3 minutes ubuntu

Todo lo que hagamos desde el contenedor main se verá reflejado en el host

Esto tiene la ventaja de que podemos instalar programas en un contenedor que tiene acceso al host para no tener que instalarlos en el host. Por ejemplo dive es una herramienta para explorar contenedores, pero si no la quieres instalar en el host la puedes instalar en un contenedor con acceso al host, así desde ese contenedor main puedes explorar el resto de contenedores sin tener que instalarla en el host

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