Docker Compose: .env

Compose nos permite una gran personalización mediante el archivo de configuración pero hay algunas cosas que no están soportadas dentro del mismo. Para algunas de estas configuraciones existen parámetros que se pueden establecer en la línea de comando o mediante variables de entorno.

Estas formas son poco prácticas porque implican acordarse los parámetros o nombres de las variables y siempre están sujetas al riesgo de los errores de tipeo. Por suerte, Compose, automáticamente levanta el archivo .env y establece las variables durante el tiempo de ejecución.

Hay que tener en cuenta que estas variables no son visibles para los contenedores sino para Docker Compose.

Hay varias, pero las más interesantes son:

  • COMPOSE_PROJECT_NAME: Permite establecer el nombre del proyecto. Esta cadena es la que usa Docker Compose como prefijo al momento de crear los containers. Por defecto utiliza el nombre de la carpeta actual.
  • COMPOSE_FILE: Para poder indicar el nombre del archivo de configuración que utilizará Docker Compose. Sirve para poder tener configurados distintos entornos ,por ejemplo, para desarrollo y para testing. Por defecto, docker-compose.yml.
  • DOCKER_HOST: En caso de que queramos utilizar el servicio de docker de un host remoto. Por ejemplo, tcp://192.168.0.13:2375. El valor por defecto apunta al socket local.
  • DOCKER_TLS_VERIFY: Si es necesario utilizar TLS para conectarse al servicio de Docker, alcanza con establecer a cualquier valor aunque generalmente se establece a 1. Si no hace falta utilizar TLS no hay que setear la variable porque cualquier valor (incluidos vacio, cero, false, étc) van a habilitarlo.
  • DOCKER_CERT_PATH: Si vamos a utilizar TLS vamos a necesitar certificados. Por defecto busca los archivos ca.pem, cert.pem y key.pem en la carpeta ~/.docker pero podemos establecer la que más nos convenga.

Docker: WordPress

Siguiendo con el tema de Docker, uno de mis usos preferidos es utilizarlo para crear entornos locales para testear WordPress. Si bien hay una imagen oficial de WordPress para Docker su configuración out-of-the-box necesita algunos ajustes para sernos de utilidad.

Primero, el archivo docker-compose.yml:

version: "3"

services:
  mysql:
    image: mysql:5.7
    restart: "no"
    volumes:
      - ./wordpress/database:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: wordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

  wordpress:
    depends_on:
      - mysql
    image: wordpress
    ports:
      - 8080:80
    restart: "no"
    volumes:
      - ./wordpress/content:/var/www/html/wp-content
      - ./wordpress/.htaccess:/var/www/html/.htaccess
      - ./wordpress/php.ini:/usr/local/etc/php/conf.d/wordpress.ini
      - <build dir>:/var/www/html/wp-content/themes/<theme name>
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DEBUG: 'true'

  adminer:
    depends_on:
      - mysql
    image: adminer
    restart: "no"
    ports:
      - 8000:8080
    environment:
      ADMINER_DEFAULT_SERVER: mysql

Como vemos, trabajo con 3 images: mysql, wordpress y adminer.

En la primera, mysql, creo un volumen que apunta a ./wordpress/database en el host y a /var/lib/mysql en el container lo que me permite persistir la información de la base de datos mediante el sencillo proceso de almacenar los archivos de datos de MySQL en el host. Es importante que especifiquemos la versión de MySQL en vez de usar latest, para evitar que los datos se pierdan al actualizarse el servidor.

La segunda, wordpress, tiene 4 volumenes creados:

  • ./wordpress/content, nos permite persistir plugins, themes, actualizaciones, étc.
  • ./wordpress/.htaccess, nos sirve para poder trabajar con el .htaccess de WordPress. En mi caso lo utilizo para redirigir las solicitudes a la carpeta uploads al servidor de producción para no tener que replicarla cada vez que creo un container para hacer pruebas.
  • ./wordpress/php.ini, nos da la posibilidad de configurar algunos parámetros de PHP. Son de particular interés el tamaño máximo de los uploads y el tiempo de ejecución ya que los por defecto son muy chicos.
  • <build dir>, este es el directorio donde se encuentra la build del theme (o plugin) con el que estemos trabajando.

Con respecto a las configuraciones, si no hicimos ningún cambio en las variables de la BBDD, no hace falta tocarlas. Si no necesitamos que esté activado el debug podemos borrar la susodicha configuración o ponerla en ‘false’.

adminer, necesario para poder trastear con la BBDD. La única configuración que pasamos es más un QOL que otra cosa y sirve para establecer el servidor por defecto.

Una cosa importante a tener en cuenta es que, como Docker crea las carpetas correspondientes a los volúmenes con el usuario root, tenemos que cambiar el owner por el usuario www-data. Para esto, una vez creado los container, alcanza con ejecutar el siguiente comando:

docker-compose exec wordpress chown www-data:www-data /var/www/html/wp-content /var/www/html/wp-content/themes

Por último dejo el archivo .htaccess y php.ini que utilizo con las características antes comentadas:

<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteBase /
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{HTTP_HOST} ^localhost:8080$
  RewriteRule ^wp-content/uploads/(.*)$ https://server/wp-content/uploads/$1 [NC,L]
</IfModule>
file_uploads = On
memory_limit = 64M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600

Docker

Hace unos meses como consecuencia de haber tenido que cambiar de discos en mi PC me encontré con la necesidad de volver a montar un entorno local de desarrollo. Como buen programador, decidí que montar todo igual que antes era demasiado sencillo y decidí revisar desde cero mi stack.

El primer cambio importante fue el abandonar Windows 10 + WSL por Ubuntu 18.04. Hay dos razones principales para esta decisión: la velocidad de I/O y los contenedores. Ya sea por el manejo de la fragmentación o falta de DNLC, los problemas de NTFS con los directorios con gran cantidad de archivos son históricos y agregarle una capa más de abstracción como es el subsistema de Linux no ayuda. Si agregamos que WSL no soporta cgroups por lo que no nos permite ejecutar nativamente containers el salto de un SO a otro es un paso lógico.

Anteriormente tenía en Windows instalado XAMPP y mediante el uso de junctions, el archivo hosts y algunos truquillos más codeaba en VSCode, utilizaba Webpack, Gulp, étc en WSL y visualizaba en un navegador en Windows.

Este workflow funcionaba en general bastante bien pero presentaba una rigidez notable y era poco práctico para testear cosas nuevas. Por esto, con el paso a Linux, la instalación de un sistema de gestión de contenedores era un must. La opción inicial era LXD pero este resultó estar más orientado a contenedores de SO que de aplicaciones. Si bien da mucha más flexibilidad para la configuración implica también más intervención del usuario mientras que Docker agrega una capa de abstracción que automatiza mucho de estos procesos lo que se adaptaba mejor a mis necesidades.

Primeras impresiones

Docker Compose es, diría, indispensable para el uso diario de Docker. Un simple archivo yaml permite configurar en unas pocas líneas una aplicación de múltiples contenedores.

En un principio el sistema de imágenes parece poco personalizable hasta que se descubre la posibilidad de montar «volúmenes» que, a los efectos prácticos, no son más que los tradicionales soft links entre el host y el contenedor. Esta posibilidad de compartir archivos y directorios nos permite, por ejemplo, externalizar en un repositorio git los archivos de configuración para mantenerla fácilmente sincronizada. O hacer lo mismo con la carpeta de datos de MySQL para volver los mismos persistentes entre distintos contenedores o replicar fácilmente la BBDD en producción.

La suma de los dos puntos anteiores vuelve el montar sistemas de prueba algo eficiente y completamente automatizable lo que repercute mucho en las ganas de hacer y usar los tests y sus beneficios asociados.

Habiendo trabajado antes con VM las diferencias de uso de recursos y tiempo de warm up y mantenimiento con Docker son bastante grandes. La configuración de los network bridge son un problema que queda en manos de Docker liberándonos de una tarea, por lo menos en desarrollo, que suele mostrarse por demás conflictiva. Esto no significa que no podamos trabajar con redes pero si que no es necesario hacerlo con Docker out-of-the-box en entornos privados.

Los repositorios públicos de imágenes son uno de los fuertes de Docker. Hasta el momento no me pasó de encontrarme sin una aplicación que necesitara y no tuviera su respectiva imagen.

Limitaciones

Como siempre, no todo es un camino de rosas y Docker presenta algunas limitaciones y problemas que pueden hacer que nos agarremos la cabeza hasta que le encontremos la vuelta para evitarlos.

Si bien Docker, y los containers en general, son una gran mejora sobre las VM todavía presentan un overhead que para los costes de servidor en el tercer mundo son demasiado altos por lo que por ahora el deploy va a seguir siendo bare metal o VPS.

El manejo de permisos de los volumenes es complejo, a los efectos prácticos estamos compartiendo archivos y carpetas entre dos SO diferentes, con usuarios diferentes. Sumado a eso, por defecto, los comandos se ejecutan como root dentro de los containers y, por lo tanto, los archivos creados por esos comandos suelen pertenecer al mismo usuario y grupo mientras que los que creamos en el host suelen tener permisos para nuestro usuario.

Todo en Docker está pensado para ocultar las complejidades del manejo de contenedores y esto inevitablemente nos lleva a soslayar los problemas de seguridad. Por ejemplo, por defecto, los containers no tienen ningún límite a los recursos que pueden utilizar; como siempre que utilizamos recursos públicos, únicamente un análisis de seguridad o usando self baked images nos podemos asegurar que no estemos utilizando una rogue image con backdoors; étc.

Trucos y consejos

En Linux los permisos se manejan con el UID y el GID, no con el nombre de usuario y grupo. Si en nuestro container el id de www-data coincide con el de nuestro usuario en el host, el manejo de permisos se vuelve más sencillo. Este truco es conocido por los que usan Docker hace más tiempo e incluso algunas imágenes, como la de PHP, lo soportan por defecto.

Este es más un truco de Bash que de Docker pero igualmente útil. Muchas veces necesitamos trabajar con todos los containers activos lo que es sumamente engorroso porque implica ejecutar docker por cada container. Para evitar esto podemos utilizar la sustitución de comandos de Bash de la siguiente forma:

docker stop $(docker ps -q)

Este comando va a ejecutar stop en todos los containers activos.

Para poder ver en tiempo real los logs de un container de Docker (o de varios si usamos Docker Compose):

docker logs -f <contenedor>

Hacer y restaurar backups de nuestros container es muy sencillo:

# Hacer el backup del contenedor a una imagen
docker commit -p <container id> <image name>

# Exportar la imagen a un archivo
docker save -o <filename.tar> <image name>

# Restaurar una imagen a partir de un archivo
docker load -i <filename.tar>

# Restaurar (crear) un contenedor a partir de la imagen
docker run <image name>

Por último, el parámetro ports de Docker Compose sirve para exponer puertos del container en el host pero su formato en vez de ser origen:destino como resulta intuitivo es exactamente al revés. Nada que sea complicado pero recordarlo evitará darnos la cabeza contra la pared cuando no sepamos por qué no funciona nuestro container.