.. _docker-compose: Composición *********** :program:`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 Fundamentos =========== El uso de :program:`docker-compose` se basa en la creación, dentro de un directorio de trabajo, de un fichero :file:`docker-compose.yaml` (o :file:`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\ [#]_. Ejemplo ilustrativo =================== Supongamos que deseamos levantar un servicio web con :ref:`nginx ` que sea capaz de generar páginas escritas en |PHP|. Para ello dispondremos dos contenedores: * Uno que ejecute :ref:`nginx``, que parta de una `imagen de Alpine `_ e instale :ref:`nginx `\ [#]_. * Otro que sea capaz de ejecutar |FPM| y que tomaremos directamente de `una de las imágenes oficiales de PHP `_ Para llevar a cabo este propósito crearemos un directorio de trabajo con el siguiente contenido: .. code-block:: none + /tmp/nginx+php | +-- nginx | +-- default.conf | +-- Dockerfile | +-- webapp | +-- index.php | +-- docker-compose.yaml donde :file:`Dockerfile` permite construir una imagen con :ref:`nginx `: .. code-block:: docker 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|: .. code-block:: nginx 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; } } .. note:: 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 :file:`webapp` es el directorio donde se almacenará la aplicación |PHP|. Nos limitaremos a utilizar el típico ejemplo:: # echo '' > webapp/index.php Y, por último, el :file:`docker-compose.yaml`: .. code-block:: 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 .. note:: 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: .. code-block:: yaml 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_: .. code-block:: yaml build: https://github.com/miusuario/repositorio.git En este caso, :kbd:`image` sirve únicamente para darle nombre a la imagen construida. Para poner en funcionamiento ambos contenedores basta con encontrarse en el directorio :file:`nginx+php` y ejecutar\ [#]_:: # 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\ [#]_, aunque cambién los :file:`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: .. code-block:: yaml volumes: socket: y haber hecho referencia a este volumen en uno de los servicios: .. code-block:: yaml 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, :command:`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*): .. code-block:: yaml 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: .. code-block:: yaml services: php: image: php:fpm-alpine volumes: - ./webapp:/srv/www - socket: /var/run/php-fpm networks: - mired .. rubric:: Notas al pie .. [#] Siempre y cuando no se declaren redes, que `también es posible `_. .. [#] Es más apropiado utilizar directamente la imagen oficial *nginx:alpine*, pero de esta forma se ilustra cómo :program:`docker-compose` también es capaz de construir imágenes. .. [#] Sin -d, podremos observar el registro y depurar el funcionamiento. .. [#] En realidad, la imagen no se genera si se encuentra una imagen en el repositorio local con idéntico nombre y etiqueta. Por ejemplo, si a la imagen asociada al servicio *web* ya descrito la hubiéramos llamado *nginx:alpine* y hubiéramos descargado previamente una imagen con ese nombre de `Docker Hub`_ (que existe realmente), entonces :program:`docker-compose` jamás habría construido la imagen homónima. .. |YAML| replace:: :abbr:`YAML (YAML Ain't Markup Language)` .. |PHP| replace:: :abbr:`PHP (PHP HyperText Preprocessor)` .. |FPM| replace:: :abbr:`FPM (FastCGI Process Manager)` .. |URL| replace:: :abbr:`URL (Uniform Resource Locator)` .. _Docker Hub: https://hub.docker.com .. _GitHub: https://github.com