Docker Compose
https://docs.docker.com/compose/
Docker Compose es una herramienta para definir y ejecutar varios contenedores de una sola vez. Es la solución para configurar un entorno completo sin necesidad de levantar contenedores uno por uno.
Docker Compose no elimina el uso del Dockerfile. Los propósitos son diferentes. Dockerfile crea imágenes y Compose inicia un conjunto de imágenes.
Instalación de Docker Compose
https://docs.docker.com/compose/install/ Los prerrequisitos son tener Docker instalado y el Docker CLI.
Nada más simple que descargar el binario, darle permisos de ejecución y colocarlo en una ubicación que esté en el path.
Configura el proyecto para obtener la última versión en caso de que el curl a continuación no esté actualizado.
https://github.com/docker/compose/releases
sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose.yml
El archivo docker-compose.yml define las configuraciones de los contenedores que se levantarán, así como las redes y volúmenes. Es un archivo YAML, por lo que es necesario conocer su estructura.
Los parámetros del archivo docker-compose pueden variar según las diferentes versiones del docker-engine, así que siempre verifiquen la compatibilidad. Consulta la versión de tu Compose con la compatibilidad de tu engine en https://docs.docker.com/compose/compose-file/compose-versioning/.
Los parámetros y especificaciones de un archivo docker-compose están en https://docs.docker.com/compose/compose-file/.
El docker-compose.yml se define así:
# obligatorio especificar la versión
version: '3.8'
# volumes si existe
volumes:
logvolume01: {}
# network si se define, sin embargo docker compose siempre crea una bridge para ese entorno aunque no se defina
networks:
app-net:
# services son los contenedores
services:
web: # nombre del contenedor
build: . # Si se construye
ports: # mapeo de puertos
- 5000:5000
volumes: # mapeo de volúmenes
- logvolume01:/var/log
network: # driver de red
- app_net
redis: # otro contenedor ....
image: redis
network:
- app_net
Es obligatorio especificar la versión debido a las diferencias entre ellas y sus funcionalidades, para evitar incompatibilidades, debe ser la primera línea. El orden de networks, volumes y services no importa.
Creando Composes
Compose simplemente ejecuta varios comandos docker por ti. Recordemos algunos flags del comando docker container run: https://docs.docker.com/engine/reference/commandline/container_run/
--volume
--name
--publish o -p
--entrypoint
--hostname
--env o -e
--ip
--mount
--network
--restart
Existen muchos otros, por lo que estos flags también se especifican dentro de un docker compose, a veces con otros nombres.
Crea un directorio para almacenar nuestro compose
mkdir ~/miproyecto
cd ~/miproyecto
Vamos a utilizar el siguiente Dockerfile para crear una imagen, pero también podríamos utilizar una imagen lista.
Crea el Dockerfile en la carpeta del compose.
cat << EOF > ~/compose/Dockerfile
FROM debian
RUN apt-get update && apt-get install wget git apache2 -yq
EXPOSE 80
CMD ["apachectl", "-D", "FOREGROUND"]
EOF
Ahora vamos a definir el siguiente docker-compose.yaml
cat << EOF > ~/mi_proyecto/docker-compose.yaml
version: '3.8'
services:
webserver: # --name
build: .
hostname: webserver # --hostname
ports: # --publish
- 80:80
restart: always # --restart
EOF
Ejecuta la creación del entorno
vagrant@worker1:~/_mi_proyecto$ docker-compose up -d
# eliminado ....
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 1/1
⠿ Container mi_proyecto-webserver-1 Started 0.4s
# Prueba el curl para ver si está respondiendo
vagrant@worker1:~/mi_proyecto$ curl localhost
# Salida yaml eliminada...
El comando docker-compose up con el parámetro -d inicia el compose de forma detached
El comando docker-compose busca el docker-compose en el directorio actual, pero es posible pasar la ruta con la opción "-f ruta_del_compose Ejemplo:
docker-compose -f ~/compose/docker-compose.yaml up -d
Para listar los contenedores creados por compose podemos utilizar el parámetro ps
vagrant@worker1:~/mi_proyecto$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
mi_proyecto-webserver-1 "apachectl -D FOREGR…" webserver running 0.0.0.0:80->80/tcp, :::80->80/tcp
Observa las imágenes
vagrant@worker1:~/mi_proyecto$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
# Crea la imagen con el nombre de la carpeta donde está el archivo y el nombre del servicio
mi_proyecto_webserver latest dc964e587ee9 19 minutes ago 303MB
nginx latest 55f4b40fe486 6 days ago 142MB
debian latest d2780094a226 6 days ago 124MB
busybox latest 62aedd01bd85 3 weeks ago 1.24MB
ubuntu latest 27941809078c 3 weeks ago 77.8MB
alpine latest e66264b98777 5 weeks ago 5.53MB
hello-world latest feb5d9fea6a5 9 months ago 13.3kB
Para detener los contenedores creados con compose utilizamos stop
vagrant@worker1:~/mi_proyecto$ docker-compose stop
[+] Running 1/1
⠿ Container mi_proyecto-webserver-1 Stopped 10.2s
# Observa que el estado quedó en exited
vagrant@worker1:~/mi_proyecto$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
mi_proyecto-webserver-1 "apachectl -D FOREGR…" webserver exited (137)
vagrant@worker1:~/mi_proyecto$
Para iniciar utilizamos start
vagrant@worker1:~/mi_proyecto$ docker-compose start
[+] Running 1/1
⠿ Container mi_proyecto-webserver-1 Started 0.4s
vagrant@worker1:~/mi_proyecto$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
mi_proyecto-webserver-1 "apachectl -D FOREGR…" webserver running 0.0.0.0:80->80/tcp, :::80->80/tcp
vagrant@worker1:~/mi_proyecto$
Una curiosidad es que docker-compose siempre crea una red bridge aunque no la definas para crear un aislamiento del proyecto.
vagrant@worker1:~/mi_proyecto$ docker network ls
NETWORK ID NAME DRIVER SCOPE
86e2c2af5201 bridge bridge local
51533d503c46 host host local
# Red creada automáticamente
3ebeac156156 mi_proyecto_default bridge local
4a528ce35bc2 none null local
Para destruir el entorno utilizamos down
vagrant@worker1:~/mi_proyecto$ docker-compose down
[+] Running 2/2
⠿ Container mi_proyecto-webserver-1 Removed 10.2s
⠿ Network mi_proyecto_default Removed 0.1s
# Nada ejecutándose
vagrant@worker1:~/mi_proyecto$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
# Pero no elimina la imagen
vagrant@worker1:~/mi_proyecto$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mi_proyecto_webserver latest dc964e587ee9 34 minutes ago 303MB
nginx latest 55f4b40fe486 6 days ago 142MB
debian latest d2780094a226 6 days ago 124MB
busybox latest 62aedd01bd85 3 weeks ago 1.24MB
ubuntu latest 27941809078c 3 weeks ago 77.8MB
alpine latest e66264b98777 5 weeks ago 5.53MB
hello-world latest feb5d9fea6a5 9 months ago 13.3kB
# elimina la red
vagrant@worker1:~/mi_proyecto$ docker network ls
NETWORK ID NAME DRIVER SCOPE
86e2c2af5201 bridge bridge local
51533d503c46 host host local
4a528ce35bc2 none null local
Ahora vamos a crear una página web para mostrar en nuestro contenedor a través de docker-compose, crea la carpeta html y el archivo index.html
vagrant@worker1:~/mi_proyecto$ mkdir ~/mi_proyecto/html
vagrant@worker1:~/mi_proyecto$ echo "<h1> Prueba de HTML </h1>" > ~/mi_proyecto/html/index.html
Vamos a subir a DockerHub la imagen que fue creada con el compose anterior
vagrant@worker1:~/mi_proyecto$ docker tag mi_proyecto_webserver:latest davidpuziol/webserver:latest
vagrant@worker1:~/mi_proyecto$ docker push davidpuziol/webserver
Ahora que ya tenemos una imagen, vamos a usarla en compose en lugar de construir una imagen.
Ahora vamos a reemplazar el docker-compose por este incluyendo un volumen bind para el html creado.
cat << EOF > ~/mi_proyecto/docker-compose.yaml
version: '3.8'
services:
webserver: # --name
image: davidpuziol/webserver
hostname: webserver # --hostname
ports: # --publish
- 80:80
volumes: # haciendo un bind
# El parámetro $PWD obtiene el directorio actual
- $PWD/html:/var/www/html
restart: always # --restart
EOF
vagrant@worker1:~/mi_proyecto$ docker-compose up -d
[+] Running 2/2
⠿ Network mi_proyecto_default Created 0.1s
⠿ Container mi_proyecto-webserver-1 Started 0.5s
vagrant@worker1:~/mi_proyecto$ curl localhost
<h1> Prueba de HTML </h1>
# Destruye el entorno
vagrant@worker1:~/mi_proyecto$ docker-compose down
Compose Multi-contenedores
La principal funcionalidad de un compose es levantar varios servicios al mismo tiempo creando un entorno completo. Vamos a levantar un WordPress y una base de datos. También vamos a especificar un archivo docker-compose.yaml diferente al estándar.
Se hicieron algunos comentarios en el compose a continuación para mostrar cómo compose traduce el comando docker. Otro detalle es que se creó una red user defined para que los contenedores se conozcan entre sí por nombre.
version: '3.8'
# docker volume create mysqldb
volumes:
mysqldb:
# docker network create wordpressnet
networks:
wordpressnet:
services:
# docker container run --name wordpress --restart always -p 80:80 -e WORDPRESS_DB_HOST=db -e WORDPRESS_DB_USER=wpuser -e WORDPRESS_DB_PASSWORD=wppassword -e WORDPRESS_DB_NAME=wordpress wordpress
wordpress:
image: wordpress
restart: always
ports:
- 80:80
environment:
# observa que el host fue apuntado por nombre usando el beneficio de la user defined network con dns activo
WORDPRESS_DB_HOST: db # podría ser db.wordpressnet
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
WORDPRESS_DB_NAME: wordpress
networks:
- wordpressnet
# solo levantará este contenedor después de db
depends_on:
- db
# docker container run --name db --restart always -v mysqldb:/var/lib/mysql -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wpuser -e MYSQL_PASSWORD=wppassword -e MYSQL_RANDOM_ROOT_PASSWORD=1 -p 3306:3306 --network wordpressnet mysql:8.0
db:
image: mysql:8.0
restart: always
volumes:
- mysql_db:/var/lib/mysql
ports:
- 3306:3306
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
MYSQL_RANDOM_ROOT_PASSWORD: '1'
networks:
- wordpressnet
cat << EOF > ~/mi_proyecto/wordpress-compose.yml
version: '3.8'
networks:
wordpressnet:
volumes:
mysqldb:
services:
wordpress:
image: wordpress
restart: always
ports:
- 80:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
WORDPRESS_DB_NAME: wordpress
networks:
- wordpressnet
depends_on:
- db
db:
image: mysql:8.0
restart: always
volumes:
- mysqldb:/var/lib/mysql
ports:
- 3306:3306
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
MYSQL_RANDOM_ROOT_PASSWORD: '1'
networks:
- wordpressnet
EOF
Ejecuta la creación del entorno.
docker-compose -f wordpress-compose.yml up -d
docker-compose ps
Accede a la dirección de la máquina y configura WordPress en tu caso.
vagrant@worker1:~/mi_proyecto$ ip -c -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp0s3 UP 10.0.2.15/24 fe80::cd:65ff:fe0c:9769/64
# es el 192.168.56.110 porque estoy en el node 1
enp0s8 UP 192.168.56.110/24 fe80::a00:27ff:fe4e:ae9f/64
docker0 DOWN 172.17.0.1/16 fe80::42:d9ff:fe79:88f1/64
br-10d3a7a94991 UP 172.24.0.1/16 fe80::42:11ff:feb7:166f/64
veth285c6b3@if41 UP fe80::b48a:44ff:fe68:691b/64
veth3a1e02b@if43 UP fe80::c8a5:4fff:fe25:c540/64
Si agregas las líneas a continuación en tu /etc/hosts puedes acceder directamente por el dominio.
192.168.56.100 master.docker-dca.example
192.168.56.110 worker1.docker-dca.example
192.168.56.120 worker2.docker-dca.example
192.168.56.200 registry.docker-dca.example

Título del Sitio: WordPress - prueba
Nombre de Usuario: user
Contraseña: password
email: [email protected]
Para verificar los logs del compose
docker-compose -f wordpress-compose.yml logs
Para ver en tiempo real agregar -f
docker-compose -f wordpress-compose.yaml logs -f
# también es posible por Linux
watch docker-compose -f wordpress.yaml logs
Para destruir el entorno también es necesario pasar el compose, de lo contrario intentará buscar el docker-compose.yaml
docker-compose -f wordpress-compose.yml down
Los volúmenes continúan persistentes en el disco, siendo posible recrear la aplicación con el mismo contenido. Créalo nuevamente y verifica en la página.
docker-compose -f wordpress-compose.yml up -d
La página de login del panel de control tiene al final /wp-admin/index.php
ejemplo: http://worker1.docker-dca.example//wp-admin/index.php
Al destruir el entorno observa el orden. Destruye en el mismo orden. Si webserver depende de db, db no puede ser destruido antes que webserver. Y la red es la última.
vagrant@worker1:~/mi_proyecto$ docker-compose -f wordpress-compose.yml down
[+] Running 3/3
⠿ Container mi_proyecto-wordpress-1 Removed 1.3s
⠿ Container mi_proyecto-db-1 Removed 1.1s
⠿ Network mi_proyecto_wordpressnet Removed
Como se mencionó anteriormente, no destruyó el volumen. Por eso podemos levantar y bajar el entorno sin problemas.
EXTRAS
Si se hace algún cambio en docker-compose y ejecutas un up, recreará lo que cambió.No es necesario hacer un down y después un up cuando se hacen cambios.
Con el parámetro--force-recreatese recreará todo el entorno incluyendo el build si existe.
Eldocker-compose pulldescarga las imágenes pero no levanta el compose
Condocker-compose buildcreas las imágenes que están en los parámetros build del compose pero no levanta el compose.
docker-compose up --buildrecrea los builds seguido de un up.
Modificando número de réplicas
Vamos a modificar nuestro primer docker compose incluyendo el parámetro deploy y eliminando el puerto. Es necesario eliminar el puerto porque intentará crear el segundo contenedor con el puerto del primero y no podrá.
cat << EOF > ~/mi_proyecto/docker-compose.yaml
version: '3.9'
services:
webserver:
build: .
hostname: webserver
deploy:
replicas: 2
volumes:
- $PWD/html:/var/www/html
restart: on-failure
EOF
vagrant@worker1:~/mi_proyecto$ docker-compose --build up -d
unknown flag: --build
vagrant@worker1:~/mi_proyecto$ docker-compose up --build -d
[+] Building 0.1s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 31B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/debian:latest 0.0s
=> [1/2] FROM docker.io/library/debian 0.0s
=> CACHED [2/2] RUN apt-get update && apt-get install wget git apache2 -yq 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:dc964e587ee9bb82042cde8857f6c02438b9a3f7511dfa9b61d53c9ca3585a62 0.0s
=> => naming to docker.io/library/mi_proyecto_webserver 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 2/2
⠿ Container mi_proyecto-webserver-2 Started 10.7s
⠿ Container mi_proyecto-webserver-1 Started 10.6s
vagrant@worker1:~/mi_proyecto$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
mi_proyecto-webserver-1 "apachectl -D FOREGR…" webserver running 80/tcp
mi_proyecto-webserver-2 "apachectl -D FOREGR…" webserver running 80/tcp
vagrant@worker1:~/mi_proyecto$
Para escalar un contenedor
docker-compose up --scale webserver=5 -d