Docker Compose
Para usar Docker de una manera mucho más cómoda, completa y mucho más profesional que con “Docker Desktop” (si vienes del artículo anterior te sonara), es recomendable usar “Docker Compose”.
Índice
- Docker básico: Usar imágenes (para todos los públicos).
- Docker Compose: Usar cómodamente imágenes (para todo quien quiera trabajar más cómodamente con Docker).
- Docker Compose en un proyecto: Uso de imágenes en un desarrollo (para aprender de verdad cualquier arquitectura de desarrollo).
- Imágenes Docker: Gestión de imágenes avanzada por consola (para usuarios avanzados).
- Dockerfile: Directivas y Dockerizar proyectos: trabajar con proyectos Dockerizados (para desarrolladores que quieran dockerizar su proyecto)
- Dockerfile avanzado: multietapa, repositorio y trucos: para especializarse en Dockerizacion (para que desarrolladores y managers controlen los detalles)
Si eres desarrollador, es necesario entender la siguiente diferencia, pues no es lo mismo usar imágenes que construir imágenes (voy a separar los conceptos para entendernos, pero te recomiendo entender ambos):
- Docker Compose: para desarrolladores que usan imágenes de terceros para desarrollar más fácil, entender todo el ciclo de vida y tener el control completo de lo que ocurre (sin depender de abstracciones de lo que hagan otros departamentos, aunque luego tengamos que juntarlo todo). Si trabajas en una empresa, da igual que no esté nada del sistema Dockerizado de la empresa en dónde trabajas, basta con que puedas instalar Docker en tu ordenador para poder aprovecharte de las ventajas del desarrollo local total y tener todas las tecnologías Docker a tu disposición.
- Dockerfile: Desarrollador que crea proyectos Dockerizados, es decir, convierte su proyecto en una imagen que luego podrá, si quiere, subir a Docker Hub (Veremos esto en un artículo más avanzado sobre Dockerfile y sus directivas). Te servirá para entender cómo Dockerizar tus proyectos.
Recordamos que Docker es una plataforma para construir, desplegar y ejecutar aplicaciones en contenedores. Esto hace que las aplicaciones sean portátiles y consistentes en diferentes entornos de ejecución.
Voy a comenzar explicando Docker de una manera para nada convencional, pero creo que es la manera más útil de sacar valor lo antes posible y sirve como continuación directa del artículo anterior (que te recomiendo leer si nunca has trabajado con Docker anteriormente, si ya tienes conocimientos de Docker puedes echarle un vistazo para afianzar la teoría, pues en este artículo voy a ir directamente a lo más práctico).
Git: el código de en este artículo también lo puedes encontrar en https://github.com/Invarato/docker/tree/main/articulos-jarroba/docker-compose
Instalar Docker
Existen dos maneras principales de instalar Docker tanto para Windows como para cualquier sistema Linux (si ya lo tienes instalado de alguna de las dos maneras, sáltate todo esto de la instalación), pero si estás empezando te recomiendo que instales “Docker Desktop” (como vimos en el artículo de Docker básico):
- “Docker CE” (Docker Comunity Edition; open source y gratis): Esta es la versión por comandos de instalación, que no incluye interfaz de usuario.
- “Docker Desktop” (también gratis): Incluye “Docker CE”, interfaz de usuario y Kubernetes local.La instalación de “Docker Desktop” lo vimos en el anterior artículo.
Puede que por alguna razón no podamos instalar “Docker Desktop” (porque no queramos o que nuestro sistema operativo no lo permita, o que seamos gente de Devops/Sistemas y no nos interese la interfaz de usuario, solo consola porque pesa menos y consume menos recursos), por lo que siempre podremos instalar “Docker CE” (Docker Comunity Edition; incluye “Docker Engine” y “Docker CLI”), que es una oficial, gratis y de código abierto. Se puede descargar gratis desde: https://docs.docker.com/engine/install/
Lo único, que no tendremos la interfaz gráfica de “Docker Desktop” y tendremos que instalar otros paquetes que nos hagan falta a mano.
Nota para empresas: de manera semejante podremos instalar la versión empresarial “Docker EE” (Docker Enterprise Edition), pero esta es de pago (más información en https://www.docker.com/pricing/ ).
Tanto si has instalado “Docker Desktop” como si has instalado “Docker CE”, para probar si tenemos bien instalado Docker, podemos usar el típico comando de comprobar la versión:
docker --version
Iniciar Docker Engine
Para poder trabajar con Docker debemos tener “Docker Engine” funcionando (En Windows se tiene que iniciar “Docker Engine” en la WSL 2 sí o sí, luego podrás trabajar con comandos Docker en la Power Shell o en el símbolo del sistema). Existen dos maneras de iniciar el Docker Engine:
- Iniciar Docker Desktop y asegurarnos que sale verde con la frase “Engine running” (esta es la manera más cómoda, pero si estás leyendo este artículo, te sugiero encarecidamente que te auto-prohibas “Docker Desktop” hasta que domines los comandos de este artículo; además, si usas Windows puede que te lie, ya que “Docker Desktop” ejecuta el servicio de Docker en Windows atacando a “WSL 2”, pero NO inicia el servicio Linux de Docker en “WSL 2”, pero SÍ funciona Docker en “WSL 2” como si estuviera iniciado, ya ves que, aunque es la manera más sencilla de usar, no es la más trivial de entender; para empezar, estés sobre Linux o Windows, mejor usa los comandos a continuación descritos, que te vas a enterar mucho mejor y luego, como premio, cuando domines todos los comandos de Docker de este artículo, te insto a que regreses a “Docker Desktop” que ayuda mucho visualmente):
- Iniciar Docker por comandos si tienes “systemd” (Ubuntu, CentOS, Debian, etc):
sudo systemctl start docker
Nota si no te funciona este comando: prueba el siguiente de “SysV init”.
Tienes otros comandos interesantes como comprobar el estado:
sudo systemctl status docker
Y detener cuando hayamos terminado:
sudo systemctl stop docker
- Iniciar Docker por comandos si tienes “SysV init” (es decir, utiliza este comando si no te funciona el anterior comando con “systemctl”, pues este te funcionará; por ejemplo, para WSL 2 de Windows):
sudo service docker start
Para comprobar el estado:
sudo service docker status
Y detener:
sudo service docker stop
Nota para iniciar “Docker Engine” en Windows: hay que iniciar “Docker Engine” en la WSL 2. Hay varias opciones, la primera abrir una terminal de WSL 2 y poner el comando como se explicó anteriormente; la segunda, iniciar “Docker Desktop” para que inicie por ti el “Docker Engine”, minimizar la interfaz gráfica y luego ya podrás usar los comandos Docker; tercero, desde cualquier terminal de Windows (símbolo del sistema o Power Shell) si antepones el comando “wsl” a cualquier de estos podrás iniciar “Docker Engine” desde la WSL 2 (esto solo es necesario para iniciar y detener el “Docker Engine”, luego podrás utilizar el símbolo de sistema o Power Shell con los comandos de “Docker” sin necesidad de poner “wsl”), por ejemplo:
wsl sudo service docker start
- Iniciar Docker por demonio, por si quieres tener “Docker Engine” en una terminal en primer plano, con su log visible (eso sí, cuando cierres esa terminal o pulses “ctrl+C”, detendrás “Docker Engine” por completo; es más recomendable los anteriores comandos, este solo para si tienes que ver qué está ocurriendo en los logs o para algo rápido):
sudo dockerd
Creo que no es necesario que te diga que, a partir de esta parte del artículo para abajo, asumiremos que tenemos “Docker Engine” funcionando (en estado “running”, por ejemplo, por haber iniciado “sudo service docker start”).
Comandos Docker (requiere “Docker Engine” funcionando)
Aquí voy a dar unas pinceladas básicas sobre los Comandos Docker que extenderemos en el siguiente artículo sobre crear proyectos con Docker Compose, puesto que este artículo vamos a necesitar muy pocos.
Antes, quiero aclarar que todos los comandos que hemos visto anteriormente (“sudo systemctl start docker”, “sudo service docker start” y “sudo dockerd”) son para iniciar el “Docker Engine”, que es necesario que esté en funcionamiento para que funcionen los comandos Docker, pero NO son comandos Docker. Los comandos Docker se caracterizan por empezar con la palabra “docker”.
Para usar comandos Docker abrimos una terminal, teniendo bien instalado Docker tendremos registrado en el sistema operativo el comando “docker” al que le seguiremos de una “acción” (un subcomando) de Docker que queremos que se ejecute y de seguido con los “parámetros” de la “acción” (argumentos del subcomando). En pseudo-comando sería algo así:
docker <acción> <argumentos de la acción>
Empecemos con uno sencillo que cumple esto anterior. Podemos buscar una imagen de Docker en el Docker Hub, por ejemplo, buscaré “mysql” (escribiremos el comando “docker”, la acción “search” y el parámetro “mysql”):
docker search mysql
Esto nos filtrará por las imágenes que contienen nuestro criterio de búsqueda (en este caso, todo lo que tenga la palabra “mysql”), en cada línea nos informará si la imagen es oficial, las estrellas que tiene, un trozo de la descripción y… poco más. Con este comando sencillo “docker search” hemos aprendido a usar comandos Docker, no obstante, sinceramente, entre tú y yo, en este caso usar la acción “search” para buscar imágenes no es la mejor opción, es mejor buscar en la web oficial del repositorio Docker Hub en https://hub.docker.com (Para este ejemplo era más sencillo haber buscado: https://hub.docker.com/search?q=mysql )
Nota sobre el resto de los comandos Docker que veremos en este artículo: siguen la misma lógica, los adelanto aquí para entender cómo se forman estos comandos, pero los detallaremos más adelante. Veremos los comandos “docker compose up” y “docker compose down”, donde “compose” será la acción Docker que vamos a querer ejecutar y “up” y “down” serán los argumentos; así mismo, veremos el comando “docker exec -it miMySQL-container /bin/bash” donde “exec” será la acción, “-it miMySQL-container” será un primer argumento y “/bin/bash” un segundo argumento. En este artículo no necesitaremos más, pero para más comandos, te sugiero revisar el siguiente artículo en el que hablaremos de cómo crear un proyecto don Docker Compose.
Docker compose
“Docker Compose” es una herramienta de Docker (es decir, que ejecutaremos como subcomando del comando “docker”, quedando “docker compose”) que permite definir y ejecutar múltiples contenedores para crear entornos personalizados. Para definir un “Docker Compose” se realiza mediante un archivo YAML llamado “docker-compose.yaml” o simplemente “compose.yaml” (éste último mejor), donde escribiremos qué contenedores queremos desplegar y cómo los queremos (sus propiedades, desde la imagen que tendrá cada contendor, hasta su volumen, variables de entorno, etc.); cuando ejecutemos nuestro fichero “compose.yaml”, Docker nos desplegará todos los contenedores que queríamos y cuando terminemos, se desmontarán todos los contenedores a la vez (piensa que quieres crear una web que se conecta a una base de datos para guardar los datos un formulario de tu web; por un lado tendrás el contenedor con el servidor web y los ficheros de tu web, y en otro contenedor la base de datos, esto lo tendremos escrito en un “compose.yaml”, por lo que podremos levantar a la vez tanto la base de datos como el servidor web y todo funcionando como si fuese uno). Además, “Docker Compose” facilita el escalado (pues si tienes un “compose.yaml” con tu base de datos y tu web, bastará con copiarlo a varios ordenadores para que tengas tu web y bases de datos ejecutándose en paralelo tantas veces como quieras). Así mismo, como desarrollador y, para lo primero que nos interesa, es para poder trabajar más rápido, entendiendo todo el ciclo de vida de lo que estemos desarrollando y separar completamente el entorno de desarrollo local del servido desde los servidores (quizás de la empresa a los que no tenemos apenas acceso, pero tenemos que trabajar con ellos).
Antes de más teoría, la mejor forma y más fácil para entender todo esto es usarlo, así que vamos a crear un “Docker Compose” muy simple, con un solo contenedor que apunta a una base de datos “MySQL”.
Recordatorio del anterior artículo
Si vienes del anterior artículo, quizás recuerdes cómo definimos un solo contenedor mediante la interfaz gráfica “Docker Desktop” (si no has leído el anterior artículo, no pasa nada, no es necesario, simplemente así es más visual), cuya captura te facilito aquí a modo de resumen, aunque no tienes que usar la interfaz gráfica de “Docker Desktop” en este artículo:
Los parámetros que nos interesarán pones son (por cierto, cosas como el puerto, las variables de entorno, la ruta donde se guardan los datos, etc. No me los he inventado, sino que están en la documentación oficial de la imagen Docker de MySQL https://hub.docker.com/_/mysql):
- Imagen Docker: “mysql:8.3”.
- Container name: “miMySQL-container”.
- Ports: “3306:3306” (donde el primer puerto es el de tu ordenador local y el segundo el del contendor; en “Docker Desktop” dejar en blanco el campo era lo mismo que poner el mismo puerto tanto en local como en el contendor, es decir: 3306:3306).
- Volumes: crearemos el Volumen de nombre “MiVolumenDocker” apuntando a “/var/lib/mysql” para persistir los datos de MySQL (en este punto espero que entiendas la diferencia entre “Volume” y “Bind mount” que vimos en el anterior artículo).
- Environment variable: Las variables de entorno serán las que definamos soportadas por la documentación oficial. Aquí definimos de “variable” como “MYSQL_ALLOW_EMPTY_PASSWORD” y en “value” la afirmación “yes”.
Crea una carpeta en cualquier sitio de tu ordenador (yo crearé una carpeta en el escritorio llamada “miDockerCompose”), dentro crea un fichero que se llame “compose.yaml” y abre este fichero con cualquier editor (yo usaré el bloc de notas, aunque mejor si usas un IDE), copia y pega el siguiente código YAML (échale un vistazo rápido, que luego paso a detallarte, pero verás que están todos los puntos que acabamos de nombrar, por lo que seguro que los deduces antes de que te cuente nada):
version: '3.8'
services:
mysql:
image: mysql:8.3
container_name: miMySQL-container
ports:
- "3306:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: true
volumes:
- MiVolumenDocker:/var/lib/mysql
volumes:
MiVolumenDocker:
Nota sobre ficheros YAML: un fichero YAML tiene un nivel principal y luego cada subnivel (lo que haya en un subnivel está dentro y pertenece al anterior) está tabulado (o con dos espacios). En el anterior fichero anterior, “services:” era nivel principal y dentro de este tenemos un subnivel llamado “mysql:”, a su vez, otro subnivel con “image: mysql:8.3”, “container_name: miMySQL-container”, etc.
Con la terminal, si vas hasta la carpeta que has creado con el fichero “compose.yaml” (con el comando: cd ruta/miDockerCompose ), podrás levantar todo lo que contenga este fichero con:
docker compose up
Nota sobre Docker antiguos: Si trabajas con un Docker antiguo, tendrás que ejecutar con guion entre “docker” y “compose”, es decir: docker-compose up
Nota sobre este ejemplo: yo voy a usar la interfaz de Windows y un símbolo del sistema para que se vea claro cada una de las partes, pero me gusta más trabajar con Linux directamente, dicho de otro modo, tú puedes hacer lo mismo en el sistema operativo que tengas.
Tras ejecutar esto y tras esperar unos segundos, se nos habrá creado el contenedor en ejecución que habíamos definido previamente en el “compose.yml”.
Ahora, queremos trabajar con este contenedor en ejecución, por lo que abrimos una nueva terminal (no cerramos la terminal anterior, sino se detendrá nuestro contenedor); entonces, para acceder a la terminal de nuestro contenedor usaremos “exec” con “-it” (“i” de interactivo para que mantenga abierta la consola y “t” de terminal para conectarnos a ella):
docker exec -it miMySQL-container /bin/bash
Dentro del contenedor podremos navegar como cualquier sistema Linux (cd, ls, etc.); aunque dentro de este contendor lo que realmente queríamos hacer era utilizar el motor de la base de datos MySql escribiendo el comando:
mysql
Por lo que así de fácil podremos crearnos nuestra base de datos y utilizar consultas sql (hay un ejemplo de consultas SQL para este contenedor en el anterior artículo de Docker básico).
Para terminar todo lo que se ha levantado desde nuestro fichero “compose.yaml”, bastará con pulsar “ctrl+C” desde la consola que ejecutamos dicho fichero (NO desde la consola en la que entramos a mysql); o también desde otra consola nueva con:
docker compose down
Nota sobre los volúmenes: Este comando anterior no elimina los volúmenes creados, puesto que nos puede interesar persistir los datos. Se puede usar “–volumes” para eliminar los volúmenes (y cualquier dato almacenado en ellos) declarados en el fichero “compose.yml” (ej: docker-compose down –volumes).
Partes de Docker Compose
Del fichero “compose.yml” que hicimos antes sin saber nada, seguro que ya has deducido un montón de cosas que ahora te describiré en detalle con algunas de las que considero las más importantes para empezar y trabajar en condiciones (para ver todas en https://docs.docker.com/compose/compose-file/ ):
- version: es la sintaxis de Docker Compose que vamos a utilizar y suele interesar poner la más moderna (a la fecha de creación de este artículo era la “3.8”, se pueden consultar la última en https://docs.docker.com/compose/compose-file/compose-versioning/) depende de la que pongamos tendremos disponibles unas tecnologías u otras, en resumen:
- Version 1: admite lo más básico (ya no se usa).
- Version 2: introduce redes, volúmenes y dependencias.
- Versión 3: introduce Docker Stack, Deploy y Docker Swarm
- services: Donde se enlistan y configuran todos los servicios que deseemos. Un servicio puede tener varios contenedores (por ejemplo, con herencia de servicios con “extends”; aunque para no liarnos, de momento podemos decir “mal” que “un servicio es un contenedor”, porque no vamos a necesitar más, así que cuando leas la palabra “servicio” en este artículo, puedes cambiarla por “contenedor”). Cada servicio tiene que estar dentro de la clave “services” y el nombre que le pongamos como clave del servicio (en el anterior ejemplo puse de clave “mysql”), será importante porque este nombre que le pongamos se usará como clave para las dependencias con otros servicios, así como IP dentro de Docker (esto no es importante en este momento, pero será bueno tenerlo presente cuando pongamos varios servicios a interactuar entre sí, puesto que si ponemos otro contenedor que queremos que apunte al que llamé “mysql”, tendremos que poner “http://mysql”, debido a que no funcionará poner “http://localhost” puesto que está en otro contenedor). Así mismo, cada servicio tendrá dentro un conjunto de configuraciones que paso a detallar (si vienes del anterior artículo muchas te sonaran, puesto que son las mismas configuraciones):
- image: la imagen que queremos usar en Docker (la que obtenemos de Docker Hub, por ejemplo, la de https://hub.docker.com/_/mysql ). Se puede intercambiar esta clave “image” por “build”, si lo que queremos es poner una ruta a Dockerfile para que se construya desde ahí (si desarrollamos un proyecto, por ejemplo, Java o Python y queremos Dockerizarlo; lo veremos más adelante).container_name: para poner un nombre personalizado al contenedor (sino se usará uno aleatorio).
- ports: mapeo de puertos; del estilo “8080:80”, siendo el de la izquierda “8080” el puerto de tu ordenador host y el “80” el de la aplicación que ese ejecuta dentro del contenedor (tienes información ampliada sobre esto en el anterior artículo).
- volumes: montaje de volúmenes. “Volume” si es el nombre de un volumen (aparte de ponerlo aquí, hay que declarar el volumen con el nombre que queramos en la clave de primer nivel “volumes”) o “Bind Mount” si es la ruta a nuestro ordenador (tienes información ampliada sobre esto en el anterior artículo).
- depends_on: aquí podrás poner los contenedores dependientes unos de otros, así el contenedor que requiera de otro se esperará a que termine de levantarse antes de iniciarse (por ejemplo, nos interesará que se levante antes la base de datos “mysql” antes que cualquier otro contenedor que lo necesite para funcionar)
- hostname: para personalizar el nombre de host del contenedor.
- environment: variables de entorno de tipo clave y valor de la forma “- VARIABLE=valor” o “VARIABLE: valor”; por ejemplo, para definir el usuario o la contraseña de las aplicaciones (en nuestro anterior compose.yml teníamos la variable de entorno: “MYSQL_ALLOW_EMPTY_PASSWORD: true”, pero puede haber otras como “MYSQL_USER: miUsuario”, “MYSQL_PASSWORD: miContrasenia”).
- env_file: Si tienes muchas variables de entorno o por razones de seguridad, en vez de escribirlas todas en el “compose.yml”, puedes crear un fichero aparte y referenciarlo aquí. Tanto “environment” como “env_file” son compatibles, es decir, los puedes usar a la vez para tener unas variables en un sitio y otras en otro.
- command: para sobrescribir comandos predeterminados de la imagen Docker. Se puede definir como una cadena única (por ejemplo: «/bin/bash -c /usr/bin/mysq» o como es una variable de entorno “mysql”) o una lista (por ejemplo: [«/bin/bash», «-c», «/usr/bin/mysql»]).
- restart: política de reinicio del contenedor (si el contendor se detiene o falla, si queremos que se vuelva a levantar, por ejemplo). Valores disponibles: “no” (no intente reiniciar, por defecto), “always” (siempre se reiniciará), “on-failure” (solo lo reiniciará si sale un código de error, es decir, destino de cero) o “unless-stopped” (siempre se reiniciará, salvo que el usuario lo detenga explícitamente).
- links: para definir una conexión entre servicios que permite a un contenedor acceder al otro mediante el nombre de servicio o un alias.
- volumes: nos permitirá definir los volúmenes que se utilizarán en los servicios (contenedores). Describo los volúmenes con más detalle en el artículo de Docker básico donde entro en más detalle en esta teoría.
- networks: Las redes Docker permiten que los contenedores se comuniquen entre sí. Para definir las redes que serán utilizadas por los servicios. Pueden ser desde redes simples hasta configuraciones más complejas, como “driver” (driver de la red a usar, como el predeterminado “bridge”, así como “host”, “overlay”, “none” y “macvlan”) y “driver_opts” (para especificar opciones de configuración específicas a un driver que estés utilizando).
- configs: donde podremos definir configuraciones que pueden ser utilizadas por los servicios en tiempo de ejecución.
- secrets: para definir información sensible (como contraseñas).
Nota sobre las redes Docker (Networks): en este artículo (y seguramente por lo general con “Docker Compose” para tu uso privado) no utilizaremos redes como tal, pero sí que usaremos “casi sin saberlo” la que está por defecto (“default”) y habrá comunicación entre los contenedores, por lo que es interesante al menos tener la noción básica de redes.
Continúa el curso de Docker
Puedes continuar con la siguiente parte de este curso en: