Digital Ocean es la plataforma que uso para hostear la mayoría de mis proyectos, incluido este blog, por lo que puedo recomendártelo sinceramente, usar este enlace no generará un costo extra sobre ti, me ayuda a mantener bajos los costos del blog y tú obtendrás $200 USD en crédito, válidos por 2 meses (60 días)
Índice del contenido
Conoce los comandos básicos de Docker Compose
Conoce los comandos básicos de Docker Compose
Docker compose nos permite crear aplicaciones con múltiples contenedores, estos contenedores interaccionarán y podrán verse entre sí. Para configurar cada uno de estos servicios usaremos un archivo en formato YAML (también le dicen YML). En este tutorial de docker compose te muestro algunos de los comandos más usados y lo que hace cada uno.
Si quieres refrescar tu memoria visita mi tutorial de comandos básicos de Docker.
¿Qué es docker compose?
Docker compose es una herramienta que te permite manejar aplicaciones que consisten en multiples contenedores de Docker. En lugar de tener múltiples Dockerfiles y estar ejecutando y vinculando uno por uno con Docker, definimos un archivo docker-compose.yml con la configuración que deseemos y lo ejecutamos, esto creará todos los servicios necesarios de nuestra aplicación. Además funciona en ambientes de desarrollo, producción, staging o testing, así como con servicios de integración continua.
Docker-compose está programando usando el lenguaje de programación go o golang ; el mismo con lenguaje con el que funcionan internamente los containers de go .
Estructura de un archivo docker-compose.yml
Así como existían los Dockerfile en Docker, donde configurabas el estado de un contenedor de manera declarativa, en Docker compose existe un equivalente: los archivos yml.
Antes de empezar con los comandos vamos a explicar la estructura de un archivo de configuración de docker-compose y algunas directrices comunes.
Un archivo de docker-compose es simplemente un archivo con extensión y formato yml. Para usarlo basta con crearlo y empezar a agregar el contenido.
touch docker-compose.yml
Estos archivos yml son increíblemente sencillos de entender.
version: '3.8'
services:
nombre_del_servicio:
variable_de_configuracion:
valores
variable_de_configuracion:
valores
nombre_de_otro_servicio:
variable_de_configuracion:
valores
Un archivo de docker-compose empieza especificando la versión de docker compose que se utilizará. Para este ejemplo usaremos la versión 3.8.
Después de la versión vienen anidada la sección de services. Puede haber tantos servicios como querramos; framework web, servidor web, base de datos, documentación, cache, etc. Cada servicio contará con sus propias variables de configuración y sus respectivos valores. Es todo, así de sencillo.
Nombres de servicios
El nombre que usemos para cada servicio en nuestro archivo yml nos servirá como una referencia para su uso en otros servicios.
Por ejemplo, si un servicio se llama “db”, es este el nombre que deberemos usar en otras aplicaciones para referirnos a un host o ubicación.
# settings.py en Django
DATABASES = {
'default': {
# ...
'HOST': 'db',
# ...
}
}
Opciones de configuración en docker compose
La personalización de un archivo docker-compose.yml depende de sus opciones de configuración, estas le dirán a cada uno de los servicios como comportarse.
Hay muchísimas variables de configuración, que puedes consultar en la documentación oficial de Docker. Para que no tengas que leerlas todas dejará algunas de las más importantes.
image
La configuración image establece la imagen a partir de la cual se generará el servicio, ideal si nuestro servicio no necesita personalización muy complicada.
version: "3.8"
services:
db:
image: postgres
build
En el caso de que necesitamos una imagen personalizada probablemente será mejor usar un Dockerfile. La opción build nos permite indicar el directorio donde este se encuentra.
Si no sabes que es un Dockerfile acá te explico como funciona y para que sirve Docker.
version: "3.8"
services:
webapp:
build: ./ubicacion_del_Dockerfile
context y dockerfile
También podemos escribir un Dockerfile personalizado, en lugar del predeterminado, especificando en context el lugar donde se encuentra y en dockerfile su nombre. Esto es bastante útil pues nos permite especificar diferentes archivos para producción o para desarrollo.
version: "3.8"
services:
webapp:
build:
context: ./ubicacion_del_Dockerfile
dockerfile: Dockerfile-personalizado
command
Command sobreescribe el comando predeterminado del contenedor. Esta opción es ideal para ejecutar un comando cuando inicia un servicio, por ejemplo un servidor web.
version: "3.8"
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
ports
Ports nos dice los puertos que expondremos al exterior y a cual puerto de nuestra máquina se vincularán, siempre en el formato de HOST:CONTENEDOR.
version: "3.8"
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
ports:
- "80:8000"
En el código de arriba el puerto 80 de nuestra máquina se corresponderá con el puerto 8000 del contenedor. Recuerda, HOST:CONTENEDOR.
También podemos especificar el protocolo udp o tcp.
version: "3.8"
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
ports:
- "80:8000/udp"
expose
Expose también expone puertos, la diferencia con ports es que los puertos solo estarán disponibles para los servicios vinculados, no para la máquina desde donde estamos ejecutando docker-compose.
version: "3.8"
services:
redis:
image: redis
expose:
- '6379'
depends_on
A veces queremos que uno de nuestros servicios se ejecute únicamente después de otro. Por ejemplo, para que un servidor web funcione correctamente es necesario tener una base de datos que ya se encuentre en funcionamiento.
depends_on nos permite que el inicio de la ejecución de un servicio dependa de otras. En palabras más sencillas, le dice a docker-compose que deseamos arrancar el servicio web solo si ya se han cargado todos los demás servicios.
version: "3.8"
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
depends_on:
- db
- redis
En el ejemplo anterior docker-compose ejecutará el servicio web solo si ya están disponibles los servicios db y redis.
environment
La configuración environment nos permite establecer una lista de variables de entorno que estarán disponibles en nuestro servicio.
version: '3.8'
services:
db:
image: postgres
environment:
- POSTGRES_USER=usuario
- POSTGRES_PASSWORD=contrasena
También podemos usar una sintaxis tipo diccionario en lugar de la de arriba.
version: '3.8'
services:
db:
image: postgres
environment:
MODE: development
DEBUG: 'true'
variables de entorno secretas
Si no especificamos un valor para una variable de entorno y dejamos su valor en blanco, docker-compose la tomará de la máquina donde se esté ejecutando docker-compose.
version: '3.8'
services:
db:
image: postgres
environment:
MODE: development
DEBUG: 'true'
SECRET_KEY:
De esta manera no tenemos que exponer información sensible si decidimos compartir nuestros archivos de Docker compose o guardarlos en un sistema de versión de controles.
env_file
Si queremos cargar múltiples variables de entorno, en lugar de especificar las variables una por una, en nuestro archivo usaremos env_file.
version: '3.8'
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
env_file: common.env
Considera que la directriz env_file carga valores dentro de los contenedores. Por lo que estas variables no estarán disponibles al momento de crear el contenedor. Por ejemplo: no puedes poner la variable PORT en un env_file y luego condicionar el puerto que expone tu servicio.
# NO ES POSIBLE ESTO
expose:
- ${PORT}
Variables de entorno con .env
Docker compose carga automáticamente un archivo llamado .env que se encuentre en la raiz del proyecto y utiliza sus variables de entorno en la configuración de sus servicios.
# Posible con un archivo.env que contenga PORT=8009
expose:
- ${PORT}
healthcheck
Este comando es para corroborar el estado de un servicio de manera periódica. Es decir podemos crear un comando que nos permita saber si nuestro contenedor está corriendo de manera correcta.
Con la configuración que se encuentra más abajo, Healtcheck va a ejecutar un curl a localhost, cada minuto y medio, una vez hayan pasado 40 segundos, si el comando tarda más de 10 segundos en devolver un resultado lo considerará como un fallo y si un fallo ocurre más de 3 veces el servicio se considerará “no saludable”.
version: '3.8'
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
env_file: common.env
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
volumes
Podemos mandar partes de nuestro sistema operativo a un servicio usando uno o varios volúmenes. Para esto usamos la sintaxis HOST:CONTENEDOR. Host puede ser una ubicación en tu sistema o también el nombre de un volumen que hayas creado con docker.
version: '3.8'
services:
db:
image: postgres:latest
volumes:
- "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
- "dbdata:/var/lib/postgresql/data"
Opcionalmente podemos especificar si el uso de volúmenes será de solo lectura o de lectura y escritura, con “ro” y “rw”, respectivamente.
version: '3.8'
services:
db:
image: postgres:latest
volumes:
- "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
- "dbdata:/var/lib/postgresql/data:ro"
restart
Con restart podemos aplicar políticas de reinicio a nuestros servicios
version: '3.8'
services:
db:
image: postgres:latest
restart: "on-failure"
La opción restart puede tomar varios valores
- no: nunca reinicia el contenedor
- always: siempre lo reinicia
- on-failure: lo reinicia si el contenedor devuelve un estado de error
- unless-stopped: lo reinicia en todos los casos excepto cuando se detiene
Comandos básicos de docker compose
Ahora que ya vimos como está conformado un archivo de docker-compose y algunas de sus configuraciones más comunes, empecemos con los comandos básicos.
Compilar un archivo de servicios
Para realizar un build a un archivo docker-compose basta con ejecutar build. Este comando buscará un archivo llamado docker-compose.yml en la carpeta actual.
docker-compose build
#Building postgres
#Step 1/4 : FROM postgres:12.3
# ---> b03968f50f0e
#Step 2/4 : COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance
# ---> Using cache
#...
Si queremos especificar un archivo docker-compose en específico usamos la opción -f, seguida del nombre del archivo.
docker-compose -f production.yml build
Ejecutar docker-compose y sus servicios
Una vez que la imagen con nuestros servicios se ha creado podemos iniciar y crear los servicios con el comando up. Con docker-compose up se empezarán o reiniciarán todos los servicios en el archivo docker-compose.yml o el que especifiquemos con -f.
docker-compose up
#Creating network "my_project" with the default driver
#Creating postgres ... done
#Creating docs ... done
#Creating django ... done
Probablemente querramos ejecutar nuestro stack de servicios en segundo plano, para eso basta con agregar la opción -d al final.
docker-compose up -d
Correr un comando dentro de un contenedor en ejecución
Para ejecutar un comando dentro un servicio que está corriendo usamos el comando docker-compose exec, seguido nombre del servicio y el comando. En este caso al ejecutar bash entramos en la terminal de nuestro servicio llamado app.
docker-compose exec app bash
Detener y remover los servicios
Detiene y remueve los contenedores, redes, volúmenes e imágenes que se crean con el comando docker-compose down.
docker-compose down
#Stopping django ... done
#Stopping docs ... done
#Stopping postgres ... done
#Removing django ... done
#Removing docs ... done
#Removing postgres ... done
#Removing network my_project
Reiniciar los servicios
Si queremos reiniciar uno o todos los servicios usamos el comando docker-compose restart.
docker-compose restart
#Restarting django ... done
#Restarting docs ... done
#Restarting postgres ... done
Para ejecutar docker-compose restart en solo a un servicio basta con colocar el nombre del servicio al final.
docker-compose restart servicio
Detener los servicios sin removerlos
Para detener uno o todos los servicios tenemos que usar docker-compose stop.
docker-compose stop
#Stopping django ... done
#Stopping docs ... done
#Stopping postgres ... done
Para ejecutar docker-compose stop a solo a un servicio basta con colocar el nombre del servicio al final.
docker-compose stop servicio
Iniciar los servicios de docker-compose sin crearlos
Podemos iniciar uno o todos los servicios con docker-compose start. Este comando es útil solo para reiniciar contenedores previamente creados pero detenidos en algún momento, además no crea ningún contenedor nuevo.
docker-compose start
#Starting postgres ... done
#Starting django ... done
#Starting docs ... done
Al añadir el nombre de un servicio al final el comando docker-compose start se ejecutará en solo ese servicio.
docker-compose start servicio
Correr un comando dentro de un servicio
Para ejecutar un comando dentro de uno de nuestros servicios usamos el comando run, la opción –rm eliminará el contenedor que se creará al terminar de ejecutarse, a continuación colocamos el comando. A diferencia de docker-compose start, este comando se usa para efectuar tareas que se llevan a cabo solo una vez.
docker-compose run --rm django python manage.py migrate
Ver los procesos
Para listar los containers que se están ejecutando
docker-compose ps
# Name Command State Ports
#-------------------------------------------------------------------------
#django /entrypoint /start Up 0.0.0.0:8000->8000/tcp
#docs /bin/sh -c make livehtml Up 0.0.0.0:7000->7000/tcp
#postgres docker-entrypoint.sh postgres Up 5432/tcp
Para listar un solo container lo colocamos al final de nuestro comando.
docker-compose ps servicio
Acceder a los procesos
De la misma manera que el comando top en GNU/Linux, docker-compose top nos muestra los procesos de cada uno de nuestros servicios.
docker-compose -f local.yml top
#django
#UID PID PPID C STIME TTY TIME CMD
#---------------------------------------------------------------------------------------------------------------------
#root 29957 29939 0 20:09 ? 00:00:00 /bin/bash /start
#...
Para ver los procesos de un único servicio basta con escribir su nombre al final del comando docker-compose top
docker-compose top servicio
Ver los logs
Si algo salió mal podemos ver los logs usando docker-compose logs. Si queremos ver los logs de un stack en específico basta con establecer nuestro archivo yml con la opción -f.
docker-compose -f production.yml logs
#Attaching to django, docs, postgres
#django | PostgreSQL is available
#django | Operations to perform:
#...
#postgres | PostgreSQL Database directory appears to contain a database; Skipping initialization
#...
#docs | sphinx-autobuild -b html --host 0.0.0.0 --port 7000 --watch /app -c . ./_source ./_build/html
De igual manera que con los demás comandos, si queremos leer los logs de un servicio es suficiente con agregar su nombre al final.
docker-compose -f production.yml logs servicio
Escalar contenedores
Antes se usaba el comando docker-compose scale para escalar los servicios. En las nuevas versiones de docker-compose escalar contenedores se realiza con el comando docker-compose up. Tras el comando agregamos la opción –scale seguida del servicio que queremos escalar y el número de copias usando el formato servicio=numero.
docker-compose -f production.yml up -d --scale servicio=3
Hay que tomar en cuenta que cuando escalamos un contenedor, se intentará crear otro contenedor con un puerto que ya estará en uso, lo que provocará un conflicto, por esta razón necesitamos especificar rangos de puertos en nuestro archivo de docker compose. Tampoco podemos usar nombres de contenenedores en nuestros servicios, por los que habrá que removerlos.
services:
django:
build:
context: .
dockerfile: Dockerfile
image: customImage
container_name: django # ES NECESARIO REMOVER ESTA LINEA
depends_on:
- postgres
volumes:
- .:/app:z
env_file:
- ./django.env
- ./posgres.env
ports:
- "8000-8005:8000" # APLICA RANGOS A LOS PUERTOS
command: /start
¿Que tal una aplicación práctica de Docker Compose? En mi siguiente entrada te explico como hacer un deploy usando cookiecutter-django y docker-compose ; gracias a cookie-cutter bastan un par de comandos de docker-compose y listo, una aplicación lista para producción, con SSL y muchas más funciones.