Warning: This information may be outdated. Please note the date of the post.



En este post vamos a dar una breve introducción de Docker:


¿Qué son los contenedores?

Son diferentes compartimentos aislados dentro de un solo sistema operativo y actúan como máquinas virtuales aunque no lo son, con esto nos referimos a que comparten muchas de las mismas características de una maquina virtual (seguridad, almacenamiento, aislamiento de redes, entre otras…), pero comparten el mismo kernel.
Las ventajas más importantes en comparación a una mv son:
  • Requieren menos recursos de hardware.
  • Son más rápidos de iniciar y finalizar.
  • Aislan librerías.
  • Facilitan el control de versiones y gestión de las mismas.
  • Minimiza el tiempo de ejecución en CPU y Almacenamiento gestionado por una aplicación, como por ejemplo al actualizar la aplicación.

¿Qué es Docker?

Docker significa ’estibador’, que si lo llevamos a un campo general es ’el que mueve los contenedores en el puerto’, le viene al pelo el nombre ¿no crees?.

Así a grandes rasgos:

Docker es un software que nos permite gestionar contenedores de aplicación en una máquina.

En más profundidad:

  • Docker fue una empresa que desarrolló este mecanismo. Cambia toda la forma del despliegue de aplicaciones.
  • Utiliza Virtualización Ligera, a nivel de sistema operativo, aprovechando mejor el hardware.
  • Aisla los recursos a nivel de kernel.
  • Aporta flexibilidad y portabilidad.
  • Gestiona contenedores a un alto nivel proporcionando todas las capas y funcionalidad adicional.
  • Cambia la forma de distribuir la aplicación. Se crea el contenedor ya funcionando, más rápido y eficaz.
  • La instalación y gestión de los contenedores es muy simple. Se necesita solo de una imagen para crearlos.
  • El proyecto nos ofrece un repositorio de imagenes (Registry Docker Hub)
  • Está escrito en GO y es software libre.
  • Está enfocado a sistemas altamente distribuidos.

Componentes de Docker

Docker engine, es propiamente el software. En el que se distinguen tres capas:

  • El demonio como tal de docker, que es el que se encarga de ejecutar el software
  • Docker [REST]API que es la capa de aplicacion que permite la comunicación con el demonio.
  • Docker CLI que es la interfaz por línea de comandos por donde nosotros podemos acceder a la API y gestionar el software.
  • Docker Registry que almacena las imágenes generadas por el Docker Engine. Se puede instalar un registro privado o uno público utilizando Docker Hub.

Instalación de Docker en Debian Buster

Si tu sistema operativo es otra distribución consulta la página oficial para instalarlo Instalar Docker según SO

Usando repositorio

  1. Actualizamos la paquetería
sudo apt-get update
  1. Nos aseguramos que tenemos los siguientes paquetes
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
  1. Añadimos la clave GPG correspondiente
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
  1. Ejecutamos el siguiente comando para configurar el repositorio estable de docker
echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Instalar Docker

 sudo apt-get update
 sudo apt-get install docker-ce docker-ce-cli containerd.io

Ejecutar Docker sin SUDO

El demonio de Docker solo se ejecuta como ROOT, normalmente usariamos sudo delante de los comandos para su uso. Para más comodidad podemos añadir nuestro usuario al grupo de docker y así nuestro usuario actual puede utilizarlo sin necesidad de ser supeusuario.

sudo usermod -aG docker $USER
Recuerda que tienes que cerrar la sesion de usuario para actualizar los grupos. O bien puedes usar este comando para una solución más rápida: exec su -l $USER

Gestión de contenedores

Estos son los comandos más usados para gestionar los contenedores:

# Conectar al contenedor 
docker attach
# Ejecutar un comando en el contendor
docker exec
# Mostrar las características del contenedor
docker inspect
# Matar un contendor
docker kill
# Ver los logs del contendor
docker logs
# Pausar o iniciar un contendor si estaba pausado
docker pause/unpause
# Ver el puerto o establecerlo
docker port 
# Ver contenedores activos / Ver todos los contenedores aunque esten inactivos
docker ps / docker ps -a
# Renombrar el contendor
docker rename 
# Inicializar/parar/reiniciar
docker start/stop/restart
# Ejecutar un contenedor a partir de una imagen
docker run 
# Ver el estado de un contendor
docker stat 
# Monitorización del contenedor 
docker top 
# Actualizar contenedor a partir de una imagen
docker update 
# Eliminar todos los contenedores 
docker rm -f $(docker ps -a -q)

Comprobar que docker está instalado correctamente

  • Actualmente tenemos la siguiente version
celiagm@debian:~$ docker --version
Docker version 20.10.8, build 3967b7d

Si ejecutamos un contendor a partir de una imagen que no está descargada la descarga directamente del docker hub. Si ya esta descargada simplemente ejecuta el contenedor.

  • De manera que ejecutamos el siguiente comando para ver que está instalado correctamente y funciona.
celiagm@debian:~$ docker run centos /bin/echo "Hello world!!"
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
7a0437f04f83: Pull complete 
Digest: sha256:5528e8b1b1719d34604c87e11dcd1c0a20bedf46e83b5632cdeac91b8c04efc1
Status: Downloaded newer image for centos:latest
Hello world!!

Comprobamos que la imagen de centos se ha descargado correctamente

celiagm@debian:~$ docker images
REPOSITORY                         TAG       IMAGE ID       CREATED        SIZE
centos                             latest    300e315adb2f   8 months ago   209MB

Ejemplo básico: Ejecutar un contenedor con Nginx

Para ello ejecutaremos el siguiente comando

docker run --name nginx-prueba -d -p 83:80 nginx

En la ejecución anterior tenemos:

  • docker run = Ejecuta el contenedor
  • --name = nombre del contenedor
  • -d = Segundo plano
  • -p = puerto al que se va exponer
  • nginx = nombreimagen:version

Comprobamos que está descargada la imagen

celiagm@debian:~$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    dd34e67e3371   6 days ago     133MB
centos       latest    300e315adb2f   8 months ago   209MB

Y que tenemos un contendor funcionando con nginx

celiagm@debian:~$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                               NAMES
dec125a8d8ab   nginx     "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:83->80/tcp, :::83->80/tcp   nginx-prueba

Si accedemos al navegador podremos ver la página inicial de nuestro nginx funcionando correctamente

docker-nginx.png

Tambien podemos ejecutar un contenedor de forma interactiva de la siguiente forma:

celiagm@debian:~$ docker run --name prueba2 -t -i nginx /bin/bash
root@3cf0feb0f6ca:/# ls
bin   dev		   docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc	

Estaríamos dentro del contenedor.

Gestión de imágenes

Las imágenes se pueden entender como plantillas de solo lectura. Son como un sistema de ficheros y parámetros listos para su ejecución (así como una receta) y están basados en un sistema operativo Linux.

Los comandos más usados para su gestión son:

# Mostrar las imágenes instaladas en nuestro host físico
docker images 
# Mostrar los cambios de una imagen 
docker history <id_imagen>
# Mostrar las caracterísitcas de la imagen 
docker inspect <id_imagen>
# Borrar una imagen 
docker rmi <id_imagen>
# Salvar o cargar una imagen 
docker save/load 

Crear imágenes. Fichero Dockerfile

Crear una imagen propia a partir de otra

Podemos crear una imagen propia a partir de otra desde la línea de comandos, pero normalmente se usa un fichero Dockerfile. Así que vamos a explicar la segunda forma.

Podemos crear una imagen a partir de un fichero Dockerfile.

Este fichero es un archivo de texto plano que contiene una serie de instrucciones necesarias para crear una imagen con el propósito de hacer funcionar una aplicación en un contendor.
Ejemplo 1: Crear imagen con Nginx

Puedes consultar la documentacion oficial para más información.

Queremos crear una imagen propia a partir de la imagen oficial de nginx.

Para ello hemos creado un directorio de trabajo previamente y en el vamos a crear un fichero Dockerfile, el cual tiene la siguiente información:

# De la siguiente imagen crearemos otra
FROM nginx
# La va mantener la persona indicada 
MAINTAINER Celia <cgarmai95@gmail.com>
# Va a ejecutar los siguientes pasos
RUN sed -i '$ d' /etc/apt/sources.list && apt-get update && apt-get install -y nano

Una vez creado el fichero vamos a ‘quemarlo’ y crear una nueva imagen propia que la vamos a llamar como queramos, es recomendable que tenga un nombre significativo, pues esta imagen si queremos la podemos subir a Docker Hub en el que previamente hemos creado el repositorio llamado ‘pruebanginx’

docker build -t cgmarquez95/pruebanginx .

Comprobamos que se ha creado la imagen

celiagm@debian:~/nginx$ docker images
REPOSITORY                TAG       IMAGE ID       CREATED         SIZE
cgmarquez95/pruebanginx   latest    9cccab4f589a   2 minutes ago   152MB
nginx                     latest    dd34e67e3371   7 days ago      133MB
debian                    latest    fe3c5de03486   7 days ago      124MB
centos                    latest    300e315adb2f   8 months ago    209MB

Ahora vamos a crear el contenedor de forma interactiva para comprobar que se ha instalado el paquete de nano como habíamos indicado anteriormente.

celiagm@debian:~/nginx$ docker run --name nginx-interactivo -ti cgmarquez95/pruebanginx /bin/bash

Arranca efectivamente con una consola y comprobamos que está instalado el paquete ’nano’

celiagm@debian:~/nginx$ docker run --name nginx-interactivo -ti cgmarquez95/pruebanginx /bin/bash
root@908dd2ff5e6a:/# dpkg -l | grep 'nano'
ii  nano                      3.2-3                       amd64        small, friendly text editor inspired by Pico
root@908dd2ff5e6a:/# apt-cache policy nano
nano:
  Installed: 3.2-3
  Candidate: 3.2-3
  Version table:
 *** 3.2-3 500
        500 http://deb.debian.org/debian buster/main amd64 Packages
        100 /var/lib/dpkg/status

Salimos del contenedor con exit.

Si usaramos ’exit’ normalmente se cierra el contenedor si queremos dejarlo en segundo plano podemos usar un atajo del teclado CTRL+q+p.

Como el contenedor está parado y le hemos hecho un cambio, vamos a hacer un commit para guardar los cambios en el repositorio.

docker commit -m "paquete nano instalado" -a "celia" nginx-interactivo cgmarquez95/pruebanginx:v1

Ahora vemos que tenemos otra imagen con la v1

celiagm@debian:~/nginx$ docker images
REPOSITORY                TAG       IMAGE ID       CREATED          SIZE
cgmarquez95/pruebanginx   v1        fa4ac22a0c73   46 seconds ago   152MB
cgmarquez95/pruebanginx   latest    9cccab4f589a   17 minutes ago   152MB
nginx                     latest    dd34e67e3371   7 days ago       133MB
debian                    latest    fe3c5de03486   7 days ago       124MB
centos                    latest    300e315adb2f   8 months ago     209MB

Si queremos subir esta version a Docker hub:

  1. Loguearse (en mi caso no me pide las credenciales porque ya estoy logueada)
celiagm@debian:~/nginx$ docker login
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /home/celiagm/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
  1. Asegurarse de que está creado el repositorio

  2. Subir la imagen

celiagm@debian:~/nginx$ docker push cgmarquez95/pruebanginx:v1
The push refers to repository [docker.io/cgmarquez95/pruebanginx]
0ab0b0b81458: Pushed 
98ccf51573dc: Pushed 
fb04ab8effa8: Mounted from library/nginx 
8f736d52032f: Mounted from library/nginx 
009f1d338b57: Mounted from library/nginx 
678bbd796838: Mounted from library/nginx 
d1279c519351: Mounted from library/nginx 
f68ef921efae: Mounted from library/nginx 
v1: digest: sha256:0decbe447f178f2f6d68f6b1a24b81d9c68395d5e34944f6e107a14b89ed1727 size: 1989

Si vamos a nuestro DockerHub estará subida.

v1.png

IMPORTANTE: Para modificar una aplicación, sea cual sea, NO se puede entrar en el contenedor y modificarla si no que BORRAMOS el contenedor actual, MODIFICAMOS LA APP y CREAMOS UN nuevo contenedor con NUEVA IMAGEN y NUEVA VERSION.
Ejemplo 2: Crear una nueva version de la app
  1. Vamos a utilizar la misma imagen que estabamos usando hasta ahora, para modificar la aplicación, en este caso, vamos a crear un fichero index.html simple (en el directorio actual)

  2. Editamos el fichero Dockerfile indicando que copie el contenido estático (index.html) a la ruta adecuada.

FROM nginx
MAINTAINER Celia <cgarmai95@gmail.com>
COPY index.html /usr/share/nginx/html/index.html
RUN sed -i '$ d' /etc/apt/sources.list && apt-get update && apt-get install -y nano
  1. Creamos la imagen nueva
docker build -t cgmarquez95/pruebanginx:v2 .

Ya tendríamos la v2

celiagm@debian:~/nginx$ docker images
REPOSITORY                TAG       IMAGE ID       CREATED             SIZE
cgmarquez95/pruebanginx   v2        5d1b348cc779   3 seconds ago       152MB
cgmarquez95/pruebanginx   v1        fa4ac22a0c73   44 minutes ago      152MB
cgmarquez95/pruebanginx   latest    9cccab4f589a   About an hour ago   152MB
nginx                     latest    dd34e67e3371   7 days ago          133MB
debian                    latest    fe3c5de03486   7 days ago          124MB
centos                    latest    300e315adb2f   8 months ago        209MB
  1. Paramos y eliminamos el contendor actual
celiagm@debian:~/nginx$ docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED          STATUS          PORTS     NAMES
908dd2ff5e6a   cgmarquez95/pruebanginx   "/docker-entrypoint.…"   55 minutes ago   Up 26 minutes   80/tcp    nginx-interactivo

celiagm@debian:~/nginx$ docker stop 908dd2ff5e6a && docker rm 908dd2ff5e6a
908dd2ff5e6a
908dd2ff5e6a
  1. Ejecutamos el NUEVO CONTENEDOR CON LA VERSION 2 de la nueva imagen
celiagm@debian:~/nginx$ docker run --name webv2 -d -p 8080:80 cgmarquez95/pruebanginx:v2
d53e8b411740cb827d391be948d757de3d07d56d4d864c6a41c16a0a9273cb16

celiagm@debian:~/nginx$ docker ps
CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS         PORTS                                   NAMES
d53e8b411740   cgmarquez95/pruebanginx:v2   "/docker-entrypoint.…"   9 minutes ago   Up 9 minutes   0.0.0.0:8080->80/tcp, :::8080->80/tcp   webv2

Funcionamiento

Comprobamos que efectivamente si accedemos al navegador al puerto 8080 indicado se ha modificado la aplicación.

v2.png

Si queremos distribuir esta nueva version solo tenemos que subirla a docker hub

docker push cgmarquez95/pruebanginx:v2

**Un ejemplo estático más visual **

Para ello vamos a tirar de un repo que tengo en mi git para la web.

El fichero Dockerfile es:

FROM nginx
MAINTAINER Celia <cgarmai95@gmail.com>
RUN sed -i '$ d' /etc/apt/sources.list && apt-get update && apt-get install -y \
        git \
        nano && mkdir /home/proyecto_htmlcss && git clone https://github.com/CeliaGMqrz/proyecto_htmlcss.git /home/proyecto_htmlcss \
        && cp -r /home/proyecto_htmlcss/* /usr/share/nginx/html/.

Se vería de la siguiente manera, haciendo el mismo procedimiento que hemos hecho hasta ahora.

v3.png

Networking en Docker

Hemos visto ejemplos de creación de contenedores a partir de imagenes propias o públicas. Ahora vamos a hablar un poco sobre las redes.

Estos son los comandos más básicos para gestionar las redes en Docker

docker network connect/disconnect
docker network create 
docker network inspect 
docker network ls 
dokcer network rm 

Docker crea tres tipos de redes al instalarlo.

En este post sólo vamos a hablar de red bridge por defecto.

celiagm@debian:~$ docker network ls
NETWORK ID     NAME       DRIVER    SCOPE
2b48025c4cf6   bridge     bridge    local
4d0c4f476c87   host       host      local
0aa9a9de8df1   none       null      local

Red bridge por defecto

Bridge, es la red por defecto. Que corresponde a la interfaz de red Docker0 que se crea en el host físico. Esta actúa como la puerta de enlace de los contenedores. Cada contenedor que se crea adopta una dirección ip en ese rango de red.

Si ejecutamos ip a en el host físico, podemos ver como se ha creado la siguiente interfaz:

9: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:65:69:c5:6f brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:65ff:fe69:c56f/64 scope link 
       valid_lft forever preferred_lft forever

Si ejecutamos un contenedor:

celiagm@debian:~$ docker run --name web -d -p 8080:80 cgmarquez95/pruebanginx:v3
d0e07f340c0442f54963ff5ab8007ade6bc0c33542ad47b52734610f57f8d492

Y entramos dentro de ese contenedor:

celiagm@debian:~$ docker exec -ti  web /bin/bash

Comprobamos que la interfaz de red ha tomado una dirección ip que está en el rango que docker ha creado.

root@d0e07f340c04:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

De igual forma, en nuestro host físico tambien podemos comprobar que se ha creado esa red de ese contenedor en concreto

17: vethce4f46a@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 72:ad:94:0a:fa:3e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::70ad:94ff:fe0a:fa3e/64 scope link 
       valid_lft forever preferred_lft forever

Si ejecutamos docker inspect web de nuestro contenedor y vamos al apartado de networks, vemos que la puerta de enlace coincide con la dirección ip de la interfaz de Docker0 del host físico.

celiagm@debian:~$ docker inspect web | grep 'Gateway'
            "Gateway": "172.17.0.1",
            "IPv6Gateway": "",
                    "Gateway": "172.17.0.1",
                    "IPv6Gateway": "",

Y también podemos comprobar la dirección ip que ha tomado en ese rango:

celiagm@debian:~$ docker inspect web | grep -i 'ipaddress'
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",

Mapeo de puertos. Reglas de iptables.

Como hemos expuesto el puerto 80 del contenedor al puerto 8080 de nuestro host físico, Docker lo que hace es crear una regla de iptables DNAT para pasar el tráfico por ahí.

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:80

Crear una red en Docker

Podemos crear una red, a esta red la vamos a llamar local1. Le indicamos que va ser de tipo bridge.

docker network create -d bridge local1

Vemos que se ha creado:

celiagm@debian:~$ docker network ls
NETWORK ID     NAME       DRIVER    SCOPE
2b48025c4cf6   bridge     bridge    local
4d0c4f476c87   host       host      local
8871b1c21717   local1     bridge    local
0aa9a9de8df1   none       null      local

En el host físico también podemos ver que se ha creado la red de tipo bridge:

18: br-8871b1c21717: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:c9:a6:56:c1 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 brd 172.19.255.255 scope global br-8871b1c21717
       valid_lft forever preferred_lft forever

En mi caso tengo el contenedor corriendo, lo vamos a parar y volvemos a ejecutarlo pero con la nueva red local que hemos creado ahora, pasándole el nuevo parámetro.

Más adelante podemos ver como conectar a la red nueva sin necesidad de parar el contenedor.

docker run --name web --network=local1 -d -p 8080:80 cgmarquez95/pruebanginx:v3

Comprobamos que ahora tiene la puerta de enlace y la direccion ip en el rango de la nueva red local1.

docker inspect web

Salida:

                   "Gateway": "172.19.0.1",
                    "IPAddress": "172.19.0.2",

Si quisieramos conectar otro contenedor ,que estuviera funcionando, a esta nueva red para interconectarlos entre sí solo tenemos que ejecutar el siguiente comando.

docker connect local1 otro_container

De esta forma ya estarían en la misma red y pueden verse entre sí.

Volumenes en Docker

Cuando ejecutamos un contenedor normalmente como hemos estado haciendo hasta ahora, cuando se cierra o se termina la información se pierde. Por lo tanto si queremos mantener esa información tenemos varias opciones:

  • Volúmenes Docker
  • tmpfs mounts
  • Bind mounts

Volúmenes Docker

Docker aporta la capacidad de crear volúmenes. Estos volúmenes no son más que un directorio que se crea en el sistema de ficheros. Dependiendo del sistema operativo se guardará en una ruta u otra. En Debian por ejemplo se almacena en /var/lib/docker/volumes.

Con estos volúmenes se permite compartir información entre contenedores, hacer copias de seguridad entre otras cosas.

Gestión de Volúmenes

Los comandos más usados son:

docker volume create 
docker volume rm 
docker volume ls 
# Elimina los volumenes que no estan siendo utilizados
docker volume prune 
docker volume inspect 

Compartiendo volumen entre un contenedor y otro

Crear volumen

Vamos a crear un volumen para guardar el contenido html de nginx, de la imagen con la que hemos estado trabajando hasta ahora.

Ademas comprobamos dónde crea Docker por defecto sus volúmenes, en este caso es en la ruta /var/lib/docker/volumes/

# Creamos el volumen 
celiagm@debian:~$ docker volume create datos_nginx
datos_nginx

# Vemos que se ha creado.
celiagm@debian:~$ docker volume ls
DRIVER    VOLUME NAME
local     datos_nginx
local     minikube

# Con inspect podemos ver el directorio que ha creado Docker donde va a guardar toda la información.

celiagm@debian:~$ docker inspect datos_nginx
[
    {
        "CreatedAt": "2021-09-07T13:34:12+02:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/datos_nginx/_data",
        "Name": "datos_nginx",
        "Options": {},
        "Scope": "local"
    }
]

Crear contenedor y asociarle el volumen creado

Vamos a crear un contenedor asociando el volumen que hemos creado, indicando donde se va a montar con la opcion -v. En este caso vamos a montar el volumen en /usr/share/nginx/html, ya que es el documenroot por defecto.

Nota: Si el volumen no está creado se creará automáticamente en la ruta por defecto de Docker.

# Creamos el contenedor asociando el volumen. 
celiagm@debian:~$ docker run -d --name my-nginx -v datos_nginx:/usr/share/nginx/html -p 5000:80 cgmarquez95/pruebanginx:v3
7c7bc77c0728e089e62f47625f6e7dc2907b4ad3dbc224aa49804561569ddf31
# Comprobamos que está funcionando 
celiagm@debian:~$ docker ps 
CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS         PORTS                                   NAMES
7c7bc77c0728   cgmarquez95/pruebanginx:v3   "/docker-entrypoint.…"   6 minutes ago   Up 6 minutes   0.0.0.0:5000->80/tcp, :::5000->80/tcp   my-nginx

Si vamos al directorio que ha creado docker en nuestro host físico podemos comprobar que se han sincronizado los directorios y tenemos todo el contenido de nuestra web.

Este directorio está gestionado directamente por Docker por lo que normalmente si no nos hemos otorgado permisos de superusuario no podremos ver el contenido. Así que entramos como root para comprobarlo.

root@debian:/var/lib/docker/volumes/datos_nginx/_data# ls
50x.html		       css    images	  info_gatos.html   README.md
convivencia_perros_gatos.html  fonts  index.html  info_perros.html  w3layouts-License.txt

Si entramos en el contenedor igualmente podemos comprobar que tenemos el volumen montado en la ruta indicada.

celiagm@debian:~$ docker exec -ti my-nginx /bin/bash
root@7c7bc77c0728:/# lsblk -f
NAME               FSTYPE LABEL UUID FSAVAIL FSUSE% MOUNTPOINT
nvme0n1                                             
|-nvme0n1p1                                         
|-nvme0n1p2                                         
|-nvme0n1p3                                         
|-nvme0n1p4                                         
`-nvme0n1p5                                         
nvme1n1                                             
|-nvme1n1p1                                         
`-nvme1n1p2                                         
  |-grupoLVM1-raiz                      291G    31% /usr/share/nginx/html
  `-grupoLVM1-swap              

La información es persistente

Si por ejemplo cambiamos algo dentro del contenedor en ese directorio html lo podemos hacer directamente y la información queda grabada. Además se sincroniza con el directorio creado por Docker en nuestro host físico.

Además si borramos el contenedor el volumen no se borra.

# Modificamos por ejemplo el index.html y le añadimos algun contenido.
root@7c7bc77c0728:/usr/share/nginx/html# nano index.html 
# Incluso añadimos un fichero de texto para la prueba 
root@7c7bc77c0728:/usr/share/nginx/html# touch prueba.txt
root@7c7bc77c0728:/usr/share/nginx/html# ls
50x.html		       css     index.html	 prueba.txt
README.md		       fonts   info_gatos.html	 w3layouts-License.txt
convivencia_perros_gatos.html  images  info_perros.html

# En el host físico comprobamos que la información se sincroniza por lo tanto es persistente.
root@debian:/var/lib/docker/volumes/datos_nginx/_data# ls -l
total 112
-rw-r--r-- 1 root root   494 jul  6 16:59 50x.html
-rw-r--r-- 1 root root  9432 ago 27 18:32 convivencia_perros_gatos.html
drwxr-xr-x 2 root root  4096 sep  7 13:34 css
drwxr-xr-x 2 root root  4096 sep  7 13:34 fonts
drwxr-xr-x 2 root root  4096 sep  7 13:34 images
-rw-r--r-- 1 root root 10755 sep  7 13:51 index.html
-rw-r--r-- 1 root root 32666 ago 27 18:32 info_gatos.html
-rw-r--r-- 1 root root 32315 ago 27 18:32 info_perros.html
-rw-r--r-- 1 root root     0 sep  7 14:00 prueba.txt
-rw-r--r-- 1 root root   590 ago 27 18:32 README.md
-rw-r--r-- 1 root root  2009 ago 27 18:32 w3layouts-License.txt

Crear nuevo contenedor y asociarle el volumen

Supongamos que queremos pasar la aplicación de nuestro nginx a un apache por lo que creamos el nuevo contenedor y le asociamos el volumen en la ruta adecuada.

# Creamos el nuevo contenedor 
docker run -d --name my-apache -v datos_nginx:/usr/local/apache2/htdocs -p 5001 httpd:2.4

#Comprobamos que están funcionando los dos 
celiagm@debian:~$ docker ps 
CONTAINER ID   IMAGE                        COMMAND                  CREATED          STATUS          PORTS                                   NAMES
98654335505b   httpd:2.4                    "httpd-foreground"       2 minutes ago    Up 2 minutes    0.0.0.0:5001->80/tcp, :::5001->80/tcp   my-apache
7c7bc77c0728   cgmarquez95/pruebanginx:v3   "/docker-entrypoint.…"   39 minutes ago   Up 39 minutes   0.0.0.0:5000->80/tcp, :::5000->80/tcp   my-nginx

Ahora si todo ha ido bien, en el navegador podemos ver que efectivamente se han salvado los datos.

Y se sirve el contenido tanto en Apache como en Nginx.

datos-nginx.png

Eliminar volumenes

Como podemos comprobar si eliminamos los contenedores los volúmenes no se eliminan, para ello usamos lo siguiente:

# Paramos y eliminamos los contenedores
celiagm@debian:~$ docker stop my-apache my-nginx && docker rm my-apache my-nginx
my-apache
my-nginx
my-apache
my-nginx
# Listamos volúmenes
celiagm@debian:~$ docker volume ls
DRIVER    VOLUME NAME
local     datos_nginx
local     minikube
# Elminamos volumen 

# Podemos hacerlo con prune que elimina los volúmenes que ya no se están utilizando. O con "docker volume rm datos_nignx"

celiagm@debian:~$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
datos_nginx

Total reclaimed space: 12.2MB

Volumenes con Bind mounts

Con el método bind mount hacemos la información persistente y estará gestionada por nosotros mismos, a diferencia de los volúmenes Docker que requieren permisos. Este sistema es muy parecido ya que se trata de montar una parte del sistema de ficheros en el contenedor. Sería así como un directorio compartido.

Crear el directorio compartido

Vamos a crear un directorio en el que vamos a alojar los datos de nuestra aplicación. En este caso será un html simple.

mkdir datos
nano index.html 

Contenido:

celiagm@debian:~/trabajo/datos$ cat index.html 
<h1>Hello World</h1>

Crear el contenedor y montar el volumen

# Creamos el contenedor indicando la ruta de nuestro directorio con el html y la ruta en la que se va a montar el volumen.
celiagm@debian:~/trabajo/datos$ docker run -d --name web1 -v /home/celiagm/trabajo/datos:/usr/local/apache2/htdocs -p 5002:80 httpd:2.4
a13d97cfdf0fece81511747c4ffdb39ae4ce22c66e3e39cc6a572bcee212fb64

# Comprobamos que efectivamente se ha montado el volumen y obtenemos nuestro html simple.
celiagm@debian:~/trabajo/datos$ curl localhost:5002
<h1>Hello World</h1>

Si paramos el contenedor y asociamos el volumen a otro contenedor la información sigue siendo persistente.

Además podemos modificar los datos directamente desde nuestro host físico

celiagm@debian:~/trabajo/datos$ curl localhost:5002
<h1>Modificado</h1>

Si inspeccionamos el contenedor y vemos los Mounts comprobaremos que están asociados los directorios.

        "Mounts": [
            {
                "Type": "bind",
                "Source": "/home/celiagm/trabajo/datos",
                "Destination": "/usr/local/apache2/htdocs",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

Eliminar contenedores y volumenes: Bind Mount

Si eliminamos los contenedores, el volumen sigue siendo persistente. En este caso como es bind mount para eliminarlos tendríamos que eliminar el directorio que hemos creado nosotros.

celiagm@debian:~/trabajo/datos$ docker stop web1 && docker rm web1
web1
web1
celiagm@debian:~/trabajo/datos$ ls
index.html

Otra opción es crear volúmenes de contenedores de datos pero eso no lo vamos a ver en este post.

Docker Compose

Hasta ahora hemos utilizado Docker para la creación de contenedores, pero ahora vamos a hablar de Docker-Compose.

Docker-Compose es una herramienta que sirve para definir y ejecutar aplicaciones con múltiples contenedores a partir de un fichero de extensión YAML. Estos contenedores por lo general están configurados de forma que interaccionan entre ellos.

Docker compose soluciona el problema de tener que repetir cada comando al levantar nuestro contenedores, ya que parte de un fichero YML en el que estan indicados todos los elementos que necesita Docker para montar nuestros escenarios de multicontenedor. Es importante el orden en el que se escriben los parámetros.

Funciona de forma similar a Vagrant (aunque no tenga nada que ver)

Por ejemplo: Queremos desplegar una aplicación web que necesita una base de datos, un servidor web y un proxy inverso. Pues a partir del fichero yml levantamos varios contenedores interconectados entre sí, cada uno haciendo su función.

Instalación de Docker-Compose


Podemos descargarlo desde la paquetería de repositorios Debian. (Actualmente no recomendado)

# Añadimos la clave GPG
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
# Buscamos el paquete 
sudo apt-cache policy docker-compose
# Lo instalamos
sudo apt-get install docker-compose

Si comprobamos la versión podemos ver que está atrasada (actualmente) en referencia al repositorio oficial.

celiagm@debian:~/docker/compose/hello-world$ docker-compose --version
docker-compose version 1.21.0, build unknown

Instalación (Versión más reciente)

Por lo que vamos a instalarlo desde el repositorio de github de la siguiente forma (así pudiendo elegir la versión más reciente):

# Descargamos el binario y lo ubicamos en el lugar adecuado
sudo curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

# Le damos los permisos necesarios 
sudo chmod +x /usr/local/bin/docker-compose

Comprobar version Docker-Compose

Comprobamos la version de docker-compose. Es importante saber qué versión estamos utilizando ya que la necesitaremos para definirla en los ficheros YAML.

Salida:

celiagm@debian:~$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c

Ejemplo: Worpress en Docker-Compose

Si vamos a DockerHub y comprobamos la documentación oficial nos da una idea de cómo podemos implementarlo con Docker-Compose

Fichero yml

Vamos a crear un directorio y en su interior el fichero yml

El fichero es similar al siguiente, (que ha sido modificado según mis datos):

Es importante el mapeo de puertos, la versión de docker-compose y las rutas para montar los volúmenes, que en mi caso se hace con ‘bind mount’

version: '3.7'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 5000:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: usuario
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: db_wp
    volumes:
      - ~/trabajo/datos/wp:/var/www/html

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: db_wp
      MYSQL_USER: usuario
      MYSQL_PASSWORD: password
      MYSQL_RANDOM_ROOT_PASSWORD: password
    volumes:
      - ~/trabajo/datos/db:/var/lib/mysql

Una vez tenemos el fichero, vamos a levantar el escenario

celiagm@debian:~/docker/compose/wordpress$ docker-compose up -d
Creating network "wordpress_default" with the default driver
Pulling wordpress (wordpress:)...
latest: Pulling from library/wordpress
f8416d8bac72: Pull complete
2259392b425a: Pull complete
cfb39fc3daf5: Pull complete
5c501de24ca4: Pull complete
ccf5f97ffc5c: Pull complete
a408db913f46: Pull complete
43600da0ccdc: Pull complete
5c02271fcd34: Pull complete
906ec01e131c: Pull complete
47000bdda098: Pull complete
4b5ef9292e8e: Pull complete
6ec123e585be: Pull complete
60a4b15c3138: Pull complete
d095bfabf7a3: Pull complete
760fcc3d35e0: Pull complete
23aa7e085fea: Pull complete
00081f376070: Pull complete
277bdab7dc90: Pull complete
f51775b484a9: Pull complete
9e5d0f8950cf: Pull complete
9401cf44b3d4: Pull complete
Digest: sha256:067833b1827e3f035c2c6b4be5336bf5bef498dafeb4f2d18258e439fa90c6f7
Status: Downloaded newer image for wordpress:latest
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
a330b6cecb98: Already exists
9c8f656c32b8: Pull complete
88e473c3f553: Pull complete
062463ea5d2f: Pull complete
daf7e3bdf4b6: Pull complete
1839c0b7aac9: Pull complete
cf0a0cfee6d0: Pull complete
fae7a809788c: Pull complete
dae5a82a61f0: Pull complete
7063da9569eb: Pull complete
51a9a9b4ef36: Pull complete
Digest: sha256:d9b934cdf6826629f8d02ea01f28b2c4ddb1ae27c32664b14867324b3e5e1291
Status: Downloaded newer image for mysql:5.7
Creating wordpress_wordpress_1 ... done
Creating wordpress_db_1        ... done

Comprobar funcionamiento

celiagm@debian:~/docker/compose/wordpress$ docker-compose ps
        Name                       Command               State                  Ports                
-----------------------------------------------------------------------------------------------------
wordpress_db_1          docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp                 
wordpress_wordpress_1   docker-entrypoint.sh apach ...   Up      0.0.0.0:5000->80/tcp,:::5000->80/tcp

Si vamos al navegador, veremos que ya podemos continuar con la instalacion de worpress

wp.png

wp1.png

Redes

Comprobamos que se ha creado una red nueva de tipo bridge

celiagm@debian:~/docker/compose/wordpress$ docker network ls
NETWORK ID     NAME                DRIVER    SCOPE
2b48025c4cf6   bridge              bridge    local
4d0c4f476c87   host                host      local
8871b1c21717   local1              bridge    local
282a523347f1   minikube            bridge    local
0aa9a9de8df1   none                null      local
ead27083973e   wordpress_default   bridge    local

Comprobaremos que la información de los volúmenes concuerdan

Volúmenes

celiagm@debian:~/trabajo/datos/db$ ls
auto.cnf    client-cert.pem  ib_buffer_pool  ib_logfile1  performance_schema  server-cert.pem
ca-key.pem  client-key.pem   ibdata1         ibtmp1       private_key.pem     server-key.pem
ca.pem      db_wp            ib_logfile0     mysql        public_key.pem      sys
celiagm@debian:~/trabajo/datos/db$ ls ../wp/
index.php        wp-admin              wp-config.php         wp-includes        wp-mail.php       xmlrpc.php
license.txt      wp-blog-header.php    wp-config-sample.php  wp-links-opml.php  wp-settings.php
readme.html      wp-comments-post.php  wp-content            wp-load.php        wp-signup.php
wp-activate.php  wp-config-docker.php  wp-cron.php           wp-login.php       wp-trackback.php

Ejemplo: Drupal + MySQL (Docker-Compose)

Drupal es un sistema de gestión de contenidos o CMS. De software libre, escrito en PHP, que cuenta con una amplia y activa comunidad de usuarios y desarrolladores. También engloba muchas más funcionalidades que no vamos a entrar en detalle para este ejemplo.
El objetivo es crear un entorno con Docker que sirva Drupal con una base de datos MySQL.

Fichero YML

Primero vamos a crear el directorio de trabajo y el fichero YAML.

version: '3.7'

# Definimos las redes que vamos a utilizar.
# - Una red interna para conectar con la base de datos.
# - Una red externa para que se pueda acceder desde el exterior.
networks:
  red_interna:
    driver: bridge
  red_externa:
    driver: bridge

# Definimos los servicios
services:
  # Definimos la base de datos
  db:
    container_name: drupal_mysql
    image: mysql:5.7
    restart: always
    # Indicamos las variables de entorno con el usuario y contraseña.
    environment:
      MYSQL_DATABASE: drupal
      MYSQL_USER: usuario
      MYSQL_PASSWORD: password
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
    # Exponemos los puertos
    ports:
    - "3306:3306"
    # Creamos un bind mount para persistir los datos
    volumes:
      - ./data/mysql:/var/lib/mysql
    # Se definen las redes
    networks:
      - red_interna
    # Nombre de la máquina
    hostname: mysql
  
  # Drupal con apache
  drupal:
    # Nombre del contenedor
    container_name: drupal
    # Depende de la base de datos mysql definida
    depends_on:
      - db
    image: drupal:8-apache
    ports:
      - 5000:80
    networks:
      - red_interna
      - red_externa
    hostname: mydrupal
    restart: always

Una vez tengamos en fichero completo vamos a desplegar el entorno

cgarcia@ws-cgarcia:~/dockerCompose/drupal$ ls
data  docker-compose.yml  log  readme.textile
cgarcia@ws-cgarcia:~/dockerCompose/drupal$ docker-compose up -d
Creating drupal_mysql ... done
Creating drupal       ... done

Comprobamos que se han desplegado los contenedores y están funcionando correctamente.

cgarcia@ws-cgarcia:~/dockerCompose/drupal$ docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED              STATUS              PORTS                                                  NAMES
af4ee7e15f3e   drupal:8-apache   "docker-php-entrypoi…"   About a minute ago   Up About a minute   0.0.0.0:5000->80/tcp, :::5000->80/tcp                  drupal
e8b43307b886   mysql:5.7         "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   drupal_mysql

Instalacion de Drupal

Si accedemos al navegador con el puerto expuesto podemos ver el instalador de drupal

Seleccionamos el idioma

idioma.png

Elegimos la instalación estandar estandar.png

Indicamos la base de datos y las credenciales configuracion.png

Procedimiento de instalación instalando.png

traducciones.png

Configuramos el sitio y se actualiza. actualizaciones.png

Sitio listo y funcionando. cms.png