.. _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