9.2.2.2.2.3. Composición

docker-compose es una aplicación que facilita la definición y ejecución de aplicaciones multicontenedor, esto es, aplicaciones que se dividen en aplicaciones más simples cada una de las cuales se ejecuta en un contenedor distinto. En particular proporciona un método declarativo para describir cómo construir varios contenedores y cómo ponerlos a funcionar simultáneamente.

Requiere una instalación independiente:

# apt install docker-compose

9.2.2.2.2.3.1. Fundamentos

El uso de docker-compose se basa en la creación, dentro de un directorio de trabajo, de un fichero docker-compose.yaml (o docker-compose.yml) donde se declaran cuáles son los contenedores, cómo se construyen sus imágenes (si es que hay que construirlas previamente), cuáles son las relaciones existentes entre ellos y cómo deben arrancarse.

Para comocer la sintaxis de estos ficheros YAML puede recurrse a la documentación oficial sobre docker-compose. Básicamente consiste en ir declarando entidades: contenedores (que denomina services), volúmenes, etc.

La puesta en marcha de los declarado resultará en la ejecución de varios contenedores incluidos dentro de una misma red[1].

9.2.2.2.2.3.2. Ejemplo ilustrativo

Supongamos que deseamos levantar un servicio web con nginx que sea capaz de generar páginas escritas en PHP. Para ello dispondremos dos contenedores:

Para llevar a cabo este propósito crearemos un directorio de trabajo con el siguiente contenido:

+ /tmp/nginx+php
      |
      +-- nginx
      |     +-- default.conf
      |     +-- Dockerfile
      |
      +-- webapp
      |     +-- index.php
      |
      +-- docker-compose.yaml

donde Dockerfile permite construir una imagen con nginx:

FROM     alpine
RUN      apk update && apk add nginx && \
         ln -s /dev/stdout /var/log/nginx/access.log;\
         ln -s /dev/stderr /var/log/nginx/error.log;\
         mkdir /run/nginx

EXPOSE   80/tcp
CMD      ["nginx", "-g", "daemon off;"]

y una configuración del servidor que nos permita ejecutar PHP:

server {
   listen 80;

   try_files $uri $uri/ =404;
   index index.php;
   root /srv/www;

   location ~ \.php$ {
      fastcgi_split_path_info ^(.+\.php)(/.+)$;

      include fastcgi.conf;
      fastcgi_param PATH_INFO $fastcgi_path_info;

      fastcgi_pass php:9000;
   }
}

Nota

Obsérvese que para conectar el servidor web con el intérprete de PHP, se utiliza como nombre de máquina php. Esto es debido a que la dirección IP de cada máquina es resoluble utilizando el nombre de servicio que se le ha asignado.

El directorio webapp es el directorio donde se almacenará la aplicación PHP. Nos limitaremos a utilizar el típico ejemplo:

# echo '<?php phpinfo(); ?>' > webapp/index.php

Y, por último, el docker-compose.yaml:

version: "3.7"
services:
  php:
    image: php:fpm-alpine
    volumes:
      - ./webapp:/srv/www

  web:
    image: alpine:nginx
    build: ./nginx
    ports:
      - "80:80"
    volumes:
      - ./webapp:/srv/www
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php

Nota

Nótese que la primera imagen se obtiene directamente del repositorio gracias a la directiva image. Al indicar sólo su nombre y entiqueta, se obtendrá del registro predefinido. No obstante, puede usarse:

image: docker.io/php:fpm-alpine

La segunda, en cambio, añade la directiva build. Esto significa que la imagen no se descarga sino que se construye a partir de la configuración que se encuentre en el directorio especificado. Además de un directorio local se puede especificar la URL de un repositorio de GitHub:

build: https://github.com/miusuario/repositorio.git

En este caso, image sirve únicamente para darle nombre a la imagen construida.

Para poner en funcionamiento ambos contenedores basta con encontrarse en el directorio nginx+php y ejecutar[3]:

# docker-compose up -d

Podemos parar la ejecución de ambos contenedores con:

# docker-compose stop

los cuales quedará listos para una ejecución posterior con:

# docker-compose start

En cambio, si queremos deshacernos de los contenedores tenemos que hacer:

# docker-compose down

lo cual, además, eliminará la red bridge asociada y los propios contenedores. No así, las imágenes que se hayan generado, que de hecho no se volverán a generar[4], aunque cambién los Dockerfile correspondientes, a menos que específicamente se indique al levantar los servicios:

# docker-compose up --build -d

Sólo hemos definido la sección services, pero si es necesario crear volúmenes de datos, podremos añadir otra sección:

volumes:
   socket:

y haber hecho referencia a este volumen en uno de los servicios:

services:
  php:
    image: php:fpm-alpine
    volumes:
      - ./webapp:/srv/www
      - socket: /var/run/php-fpm

Por otro lado, no hemos establecido ninguna regla sobre la red de ambos contenedores. Por defecto, docker-compose crea una red de usuario de tipo bridge (de ahí que los nombres de las máquinas sean resolubles).

Sin embargo podríamos haber redefinido la red que se crea por defecto añadiendo otra sección principal (al nivel de services o volumes):

networks:
   default:
      driver: bridge
      ipam:
         driver: default
         config:
            - subnet: 172.22.0.0/16

La red es la red predeterminada porque la hemos llamado «default». Podríamos haberle dado otro nombre (p.e. «mired») y haber especificado en los servicios que esa era la red que usan:

services:
  php:
    image: php:fpm-alpine
    volumes:
      - ./webapp:/srv/www
      - socket: /var/run/php-fpm
    networks:
      - mired

Notas al pie