Introducción

WordPress es un sistema de administración de contenido (CMS) gratuito y de código abierto que se desarrolla sobre una base de datos de MySQL con procesamiento PHP. Gracias a su arquitectura de complemento extensible, a su sistema de plantillas y al hecho de que la mayor parte de su administración pueda realizarse a través de la interfaz web, WordPress es una opción popular al crear diferentes tipos de sitios web, desde blogs hasta páginas de productos y sitios de comercio electrónico.

Ejecutar WordPress normalmente implica instalar una pila LAMP (Linux, Apache, MySQL y PHP) o LEMP (Linux, Nginx, MySQL y PHP), lo cual puede llevar mucho tiempo. Sin embargo, utilizando herramientas como Docker y Docker Compose, puede simplificar el proceso de configuración de su pila preferida e instalar WordPress. En lugar de instalar de forma manual componentes individuales, puede utilizar imágenes, que estandarizan aspectos como bibliotecas, archivos de configuración y variables de entorno y ejecutan estas imágenes en contenedores, que son los procesos aislados que se ejecutan en un sistema operativo compartido. Además, al utilizar Compose, puede coordinar varios contenedores, como una aplicación y una base de datos, para comunicarse entre sí.

A través de este tutorial, creará una instalación de WordPress con varios contenedores. Sus contenedores incluirán una base de datos de MySQL, un servidor web de Nginx y el propio WordPress. También protegerá su instalación obteniendo certificados TLS y SSL con Let´s Encrypt para el dominio que desee asociar a su sitio. Por último, configurará una tarea cron para renovar sus certificados de modo que su dominio permanezca seguro.

Requisitos previos

Para este tutorial, necesitará lo siguiente:

  • Un servidor con Ubuntu 18.04, un usuario no root con privilegios sudo y un firewall activo. Para obtener información sobre cómo configurarlos, consulte esta guía de configuración inicial para servidores.
  • Docker instalado en su servidor conforme a los pasos 1 y 2 de Cómo instalar y usar Docker en Ubuntu 18.04.
  • Docker Compose instalado en su servidor conforme el paso 1 de Cómo instalar Docker Compose en Ubuntu 18.04.
  • Un nombre de dominio registrado. En este tutorial, se utilizará example.com en todo momento. Puede obtener un ejemplar gratis en Freenom o utilizar el registrador de dominios que desee.
  • Los dos registros DNS que se indican a continuación se configuraron para su servidor. Puede seguir esta introducción al DNS de DigitalOcean para obtener información sobre cómo agregarlos a una cuenta de DigitalOcean, si usa una:

    • Un registro A con example.com​​​ orientado a la dirección IP pública de su servidor.
    • Un registro A con example.com​​​ ​​orientado a la dirección IP pública de su servidor.

Paso 1: Definir la configuración del servidor web

Antes de ejecutar cualquier contenedor, nuestro primer paso será definir la configuración de nuestro servidor web de Nginx. En nuestro archivo de configuración se incluirán algunos bloques de ubicación específicos de WordPress, junto con un bloque de ubicación para dirigir las solicitudes de verificación de Let´s Encrypt al cliente Certbot a fin de renovar los certificados de manera automática.

Primero, cree un directorio de proyecto para su configuración de WordPress llamado wordpress y diríjase a este:

  • mkdir wordpress && cd wordpress

A continuación, cree un directorio para el archivo de configuración:

  • mkdir nginx-conf

Abra el archivo con nano o su editor favorito:

  • nano nginx-conf/nginx.conf

En este archivo, añadiremos un bloque de servidor con directivas para el nombre del servidor y el root de documento, bloques de ubicación para dirigir la solicitud de certificados del cliente de Certbot, procesamiento de PHP, y solicitudes de recursos estáticos.

Pegue el siguiente código en el archivo. Asegúrese de sustituir example.com por su propio nombre de dominio:

~/wordpress/nginx-conf/nginx.conf
server {
        listen 80;
        listen [::]:80;

        server_name example.com www.example.com;

        index index.php index.html index.htm;

        root /var/www/html;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }

        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }

        location ~ /\.ht {
                deny all;
        }

        location = /favicon.ico {
                log_not_found off; access_log off;
        }
        location = /robots.txt {
                log_not_found off; access_log off; allow all;
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}

Nuestro bloque de servidor incluye la siguiente información:

Directivas:

  • listen: indica a Nginx que escuche en el puerto 80, lo que nos permitirá usar el complemento webroot de Certbot para nuestras solicitudes de certificados. Tenga en cuenta que aún no incluiremos el puerto 443; actualizaremos nuestra configuración para incluir SSL una vez que hayamos obtenido nuestros certificados de manera exitosa.
  • server_name: define el nombre de su servidor y el bloque de servidor que debería utilizarse para las solicitudes a este último. Asegúrese de sustituir example.com en esta línea por su propio nombre de dominio.
  • index: la directiva index define los archivos que se utilizarán como índices al procesar las solicitudes a su servidor. En este caso, modificamos el orden de prioridad predeterminado moviendo index.php en frente de index.html para que Nginx priorice los archivos llamados index.php cuando sea posible.
  • root: nuestra directiva root nombra el directorio root para las solicitudes a nuestro servidor. Este directorio, /var/www/html, se crea como punto de montaje en el momento de la compilación siguiendo instrucciones de nuestro WordPress Dockerfile. Estas instrucciones de Dockerfile también garantizan que los archivos de la versión de WordPress se monten en este volumen.

Bloques de ubicación:

  • location ~ /.well-known/acme-challenge: este bloque de ubicación manejará las solicitudes al directorio .well-known, donde Certbot creará un archivo temporal para validar la resolución del DNS de nuestro dominio en nuestro servidor. Una vez implementada esta configuración, podremos utilizar el complemento webroot de Certbot a fin de obtener certificados para nuestro dominio.
  • location /: en este bloque de ubicación, utilizaremos una directiva try_files para buscar archivos que coincidan con solicitudes de URI individuales. Sin embargo, en lugar de devolver un estado 404 Not Found por defecto, pasaremos el control al archivo index.php de WordPress con los argumentos de solicitud.
  • location ~ \.php$: este bloque de ubicación se encargará del procesamiento de PHP y servirá como proxy de estas solicitudes a nuestro contenedor de wordpress. Debido a que nuestra imagen de Docker de WordPress se basará en la imagen php:fpm, también incluiremos opciones de configuración que sean específicas para el protocolo FastCGI en este bloque. Nginx requiere un procesador de PHP independiente para solicitudes de PHP: en nuestro caso, estas solicitudes estarán a cargo del procesador php-fpm que está incluido con la imagen php:fpm. Además, este bloque de ubicación incluye directivas, variables y opciones específicas de FastCGI que actuarán como proxy para las solicitudes a la aplicación de WordPress que se ejecuten en nuestro contenedor wordpress, establecerán el índice preferido para la URI de la solicitud analizada y analizarán las solicitudes de URI.
  • location ~ /\.ht: este bloque se encargará de archivos .htaccess, ya que Nginx no los proporcionará. La directiva deny_all garantiza que los archivos de .htaccess nunca se proporcionarán a los usuarios.
  • location = /favicon.ico, location = /robots.txt: estos bloques garantizan que las solicitudes a /favicon.ico y /robots.txt no se registren.
  • location ~* \. (css|gifBlog|tox0101jpeg|jpgs|png)$: este bloque desactiva el registro para solicitudes de activos estáticos y garantiza que estos activos tengan altas condiciones de almacenamiento en caché, ya que normalmente su mantenimiento es costoso.

Para obtener más información sobre proxy de FastCGI, consulte el artículo Información e implementación de proxy de FastCGI en Nginx. Para obtener información sobre los bloques de servidor y ubicación, consulte el artículo Información sobre algoritmos de selección de bloques de servidores y ubicación de Nginx.

Guarde y cierre el archivo cuando concluya la edición. Si utiliza nano, podrá hacerlo presionando CTRL+X, Y y luego ENTER.

Una vez implementada su configuración de Nginx, puede crear variables de entorno para pasarlas a su aplicación y contenedores de bases de datos en tiempo de ejecución.

Paso 2: Definir variables de entorno

Sus contenedores de bases de datos y aplicaciones de WordPress necesitarán acceso a determinadas variables de entorno en tiempo de ejecución a fin de que los datos de su aplicación persistan y esta pueda acceder a ellos. Estas variables incluyen información confidencial y no confidencial: valores confidenciales para su contraseña root de MySQL, y usuario y contraseña de base de datos de aplicación, e información no confidencial para el nombre y host de la base de datos de su aplicación.

En lugar de establecer todos estos valores en nuestro archivo de Docker Compose, el archivo principal que contiene información sobre cómo se ejecutarán nuestros contenedores, podemos establecer los valores confidenciales en un archivo .env y restringir su circulación. Esto impedirá que estos valores se copien en los repositorios de nuestro proyecto y se expongan al público.

En el directorio principal de su proyecto, ~/wordpress, abra un archivo llamado .env:

  • nano .env

Entre los valores confidenciales que configuraremos en este archivo se incluyen una contraseña para nuestro usuario root de MySQL y un nombre de usuario y contraseña que WordPress usará para acceder a la base de datos.

Añada los siguientes nombres y valores de variables al archivo. Recuerde proporcionar aquí sus propios valores para cada variable:

~/wordpress/.env
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wordpress_database_user
MYSQL_PASSWORD=your_wordpress_database_password

Hemos incluido una contraseña para la cuenta administrativa root, además de nuestro nombre de usuario y contraseña preferidos para la base de datos de nuestra aplicación.

Guarde y cierre el archivo cuando concluya la edición.

Debido a que su archivo .env contiene información confidencial, le convendrá asegurarse de que esté incluido en los archivos .gitignore y .dockerignore de su proyecto, los cuales indican a Git y Docker qué archivos no deben copiarse a sus repositorios Git e imágenes Docker, respectivamente.

Si planea trabajar con Git para el control de versiones, inicialice su directorio de trabajo actual como un repositorio con git init:

  • git init

A continuación abra un archivo .gitignore:

  • nano .gitignore

Añada .env al archivo:

~/wordpress/.gitignore
.env

Guarde y cierre el archivo cuando concluya la edición.

Asimismo, es una buena medida de precaución añadir .env a un archivo .dockerignore, para que no termine en sus contenedores cuando esté usando este directorio como su contexto de compilación.

Abra el archivo:

  • nano .dockerignore

Añada .env al archivo:

~/wordpress/.dockerignore
.env

Debajo de esto, puede añadir de manera opcional archivos y directorios asociados con el desarrollo de su aplicación:

~/wordpress/.dockerignore
.env
.git
docker-compose.yml
.dockerignore

Guarde y cierre el archivo cuando termine.

Ahora que estableció su información confidencial, podrá definir sus servicios en un archivo docker-compose.yml.

Paso 3: Definir servicios con Docker Compose

Su archivo docker-compose.yml contendrá las definiciones de servicios para su configuración. Un servicio en Compose es un contenedor en ejecución y las definiciones de servicios especifican información sobre cómo se ejecutará cada contenedor.

Mediante Compose, puede definir diferentes servicios para ejecutar aplicaciones en varios contenedores, ya que Compose le permite establecer un enlace entre estos servicios y redes y volúmenes compartidos. Esto será útil para nuestra configuración actual, ya que crearemos diferentes contenedores para nuestra base de datos, nuestras aplicaciones de WordPress y nuestro servidor web. También crearemos un contenedor para ejecutar el cliente de Certbot, a fin de obtener certificados para nuestro servidor web.

Para comenzar, abra el archivo docker-compose.yml:

  • nano docker-compose.yml

Añada el siguiente código para definir su versión del archivo de Compose y el servicio de base de datos db:

~/wordpress/docker-compose.yml
version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

La definición del servicio db contiene las siguientes opciones:

  • image: indica a Compose la imagen que debe obtener para crear el contenedor. En este caso, fijaremos la imagen de mysql:8.0 para evitar futuros conflictos, ya que la imagen mysql:latest continúa en proceso de actualización. Para obtener más información sobre la fijación de versiones y evitar conflictos de dependencias, consulte la documentación de Docker sobre prácticas recomendadas de Dockerfile.
  • container_name: especifica un nombre para el contenedor.
  • restart: define la política de reinicio del contenedor. El valor predeterminado es no, pero configuramos el contenedor para reiniciarse a menos que se detenga de manera manual.
  • env_file: esta opción indica a Compose que deseamos añadir variables de entorno de un archivo llamado .env, ubicado en nuestro contexto de compilación. En este caso, el contexto de compilación es nuestro directorio actual.
  • environment: esta opción le permite añadir variables de entorno, más allá de las definidas en su archivo .env. Estableceremos la variable MYSQL_DATABASE de modo que sea igual a wordpress, a fin de proporcionar un nombre para la base de datos de nuestra aplicación. Debido a que se trata de información no confidencial, podemos incluirla de forma directa en el archivo docker-compose.yml.
  • volumes: aquí montaremos un volumen con nombre llamado dbdata en el directorio /var/lib/mysql del contenedor. Este es el directorio de datos estándar para MySQL en la mayoría de las distribuciones.
  • command: esta opción especifica un comando para anular la instrucción CMD predeterminada para la imagen. En nuestro caso, añadiremos una opción al comando estándar de mysqld de Docker, que inicia el servidor de MySQL en el contenedor. Esta opción, --default-authentication-plugin=mysql_native_password, fija la variable de sistema --default-authentication-plugin en mysql_native_password y especifica el mecanismo de autenticación que debe regir las nuevas solicitudes de autenticación al servidor. Debido a que PHP y, por lo tanto, nuestra imagen de WordPress no son compatibles con la nueva autenticación predeterminada de MySQL, debemos realizar este ajuste para autenticar el usuario de base de datos de nuestra aplicación.
  • networks: especifica que nuestro servicio de aplicación se unirá a la red app-network, que definiremos al final del archivo.

A continuación, debajo de su definición de servicio db, agregue la definición para el servicio de su aplicación wordpress:

~/wordpress/docker-compose.yml
...
  wordpress:
    depends_on:
      - db
    image: wordpress:5.1.1-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

En esta definición de servicio, nombraremos nuestro contenedor y definiremos una política de reinicio, como hicimos con el servicio db. También añadiremos algunas opciones específicas de este contenedor:

  • depends_on: esta opción garantiza que nuestros contenedores se inicien por orden de dependencia; el contenedor de wordpress comenzará después del contenedor de db. Nuestra aplicación de WordPress depende de la existencia de nuestra base de datos y usuario, por lo que expresar este orden de dependencia permitirá que nuestra aplicación se inicie de forma adecuada.
  • image: para esta configuración, usaremos la imagen de WordPress 5.1.1-fpm-alpine. Como se mencionó en el paso 1, usar esta imagen garantiza que nuestra aplicación tenga el procesador php-fpm que Nginx requiere para manejar el procesamiento de PHP. Esta imagen también es alpine, derivada del proyecto Alpine Linux, y permitirá que el tamaño de nuestra imagen sea en general reducido. Para obtener más información sobre las ventajas y desventajas del uso de imágenes alpine y si esto tiene o no sentido para su aplicación, consulte el análisis completo en la sección Variantes de imágenes de la página de imagenes de WordPress de Docker Hub.
  • env_file: nuevamente, especificaremos que queremos introducir valores de nuestro archivo .env, ya que es aquí donde definimos el usuario y la contraseña de la base de datos de nuestra aplicación.
  • environment: aquí, usaremos los valores que definimos en nuestro archivo .env, pero los asignaremos a los nombres de las variables previstas por la imagen de WordPress: WORDPRESS_DB_USER y WORDPRESS_DB_PASSWORD. También definiremos un WORDPRESS_DB_HOST, que será el servidor de MySQL que se ejecutará en el contenedor db al que se puede acceder en el puerto predeterminado 3306 de MySQL. Nuestro WORDPRESS_DB_NAME será el mismo valor que especificamos en la definición del servicio de MySQL para nuestra MYSQL_DATABASE: wordpress.
  • volumes: montaremos un volumen llamado wordpress en el punto de montaje /var/www/html creado por la imagen de WordPress. Usar un volumen con nombre de esta manera nos permitirá compartir el código de nuestra aplicación con otros contenedores.
  • networks: también añadiremos el contenedor wordpress a la red app-network.

A continuación, debajo de la definición del servicio de la aplicación wordpress, agregue la siguiente definición para su servicio de Nginx webserver:

~/wordpress/docker-compose.yml
...
  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

De nuevo, nombraremos nuestro contenedor y haremos que dependa del contenedor de wordpress por orden de inicio. También usaremos una imagen alpine, la imagen 1.15.12-alpine de Nginx.

Esta definición de servicio también incluye las siguientes opciones:

  • ports: expone el puerto 80 para habilitar las opciones de configuración que definimos en nuestro archivo nginx.conf en el paso 1.
  • volumes: aquí definiremos una combinación de volúmenes nombrados y montajes “bind”:
    • wordpress:/var/www/html: montará el código de nuestra aplicación de WordPress en el directorio /var/www/html, el que configuramos como root en nuestro bloque de servidor de Nginx.
    • ./nginx-conf:/etc/nginx/conf.d: vinculará mediante montaje “bind” el directorio de configuración de Nginx en el host con el directorio pertinente del contenedor, lo cual garantizará que cualquier cambio que realicemos en los archivos del host se refleje en el contenedor.
    • certbot-etc:/etc/letsencrypt: montará los certificados y las claves pertinentes de Let’s Encrypt para nuestro dominio en el directorio correspondiente del contenedor.

Una vez más, agregamos este contenedor a la red app-network.

Por último, debajo de su definición de webserver, agregue su última definición de servicio para el servicio certbot. Asegúrese de sustituir la dirección de correo electrónico y los nombres de dominio que aparecen aquí por su propia información:

~/wordpress/docker-compose.yml
  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com

Esta definición indica a Compose que introduzca la imagen certbot/certbot de Docker Hub. También utiliza volúmenes nombrados para compartir recursos con el contenedor de Nginx, incluidos los certificados de dominio y la clave de certbot-etc, y el código de la aplicación en wordpress.

Una vez más, usamos depends_on para especificar que el contenedor certbot debe iniciarse una vez que el servicio webserver esté en ejecución.

También incluimos una opción de command que especifica un subcomando que debe ejecutarse con el comando de certbot predeterminado del contenedor. El subcomando certonly obtendrá un certificado con las siguientes opciones:

  • --webroot: indica a Cerbot que utilice el complemento webroot a fin de colocar archivos en la carpeta webroot para la autenticación. Este complemento depende del método de validación HTTP-01, que utiliza una solicitud HTTP para probar que Certbot puede acceder a los recursos de un servidor que responda a un nombre de dominio determinado.
  • --webroot-path: esto especifica la ruta del directorio webroot.
  • --email: su correo electrónico preferido para el registro y la recuperación.
  • --agree-tos: especifica que acepta el Acuerdo de suscripción de ACME.
  • --no-eff-email: indica a Certbot que usted no desea compartir su correo electrónico con la Electronic Frontier Foundation (EFF). Puede omitirlo si lo prefiere.
  • --staging: indica a Certbot que desea utilizar el entorno de configuración de Let’s Encrypt para obtener certificados de prueba. Utilizar esta opción le permite probar sus opciones de configuración y evitar posibles límites vinculados a solicitudes de dominio. Para obtener más información sobre estos límites, consulte la documentación sobre los límites de tasas de Let’s Encrypt.
  • -d: le permite especificar los nombres de dominio que desee aplicar a su solicitud. En este caso, incluimos example.com y www.example.com. Asegúrese de sustituirlos por su propio dominio.

Debajo de la definición de servicio certbot, agregue sus definiciones de red y volumen:

~/wordpress/docker-compose.yml
...
volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge  

Nuestra clave de alto nivel volumes define los volúmenes certbot-etc, wordpress y dbdata. Cuando Docker crea volúmenes, el contenido del volumen se almacena en un directorio del sistema de archivos host, /var/ib/docker/volume/, que Docker administra. El contenido de cada volumen luego se monta desde este directorio en cualquier contenedor que utilice el volumen en cuestión. De esta manera, es posible compartir códigos y datos entre contenedores.

La red de puente definida por el usuario app-network permite la comunicación entre nuestros contenedores, ya que están en el mismo host de demonio de Docker. Esto agiliza el tráfico y la comunicación dentro de la aplicación, ya que abre todos los puertos entre contenedores en la misma red de puente sin exponer ningún puerto al mundo exterior. Por lo tanto, nuestros contenedores db, wordpress y webserver pueden comunicarse entre sí y solo debemos exponer el puerto 80 para el acceso de front-end a la aplicación.

El archivo docker-compose.yml terminado tendrá este aspecto:

~/wordpress/docker-compose.yml
version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: wordpress:5.1.1-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge  

Guarde y cierre el archivo cuando concluya la edición.

Una vez configuradas las definiciones de servicio, estará listo para iniciar los contenedores y probar las solicitudes de su certificado.

Paso 4: Obtener certificados y credenciales SSL

Podemos iniciar nuestros contenedores con el comando docker-compose up, que creará y ejecutará nuestros contenedores en el orden que especificamos. Si las solicitudes de nuestros dominios tienen éxito, veremos el estado de salida correcto en nuestro resultado y los certificados correctos montados en la carpeta /etc/letsencrypt/live del contenedor webserver.

Cree los contenedores con docker-compose up y el indicador -d, que ejecutarán los contenedores db, wordpress y webserver en segundo plano:

  • docker-compose up -d

Verá un resultado que confirmará la creación de sus servicios:

Output
Creating db ... done Creating wordpress ... done Creating webserver ... done Creating certbot ... done

Mediante docker-compose ps, compruebe el estado de sus servicios:

  • docker-compose ps

Si todo se realizó correctamente, el estado de sus servicios de db, wordpress y webserver será Up y el contenedor certbot se habrá cerrado con el mensaje de estado 0:

Output
Name Command State Ports ------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 db docker-entrypoint.sh --def ... Up 3306/tcp, 33060/tcp webserver nginx -g daemon off; Up 0.0.0.0:80->80/tcp wordpress docker-entrypoint.sh php-fpm Up 9000/tcp

Si ve algo diferente de Up en la columna State para los servicios db, wordpress o webserver, o un estado de salida distinto a 0 para el contenedor certbot, asegúrese de verificar los registros de servicio con el comando docker-compose logs:

  • docker-compose logs service_name

Ahora podrá verificar que sus certificados se hayan montado en el contenedor webserver con docker-compose exec:

  • docker-compose exec webserver ls -la /etc/letsencrypt/live

Si sus solicitudes de certificado se completan de forma correcta, verá un resultado como este:

Output
total 16 drwx------ 3 root root 4096 May 10 15:45 . drwxr-xr-x 9 root root 4096 May 10 15:45 .. -rw-r--r-- 1 root root 740 May 10 15:45 README drwxr-xr-x 2 root root 4096 May 10 15:45 example.com

Ahora que sabe que su solicitud será correcta, puede editar la definición de servicio certbot para eliminar el marcador --staging.

Abra docker-compose.yml:

  • nano docker-compose.yml

Encuentre la sección del archivo con la definición de servicio certbot y sustituya el indicador --staging en la opción command por el indicador --force-renewal, el cual indicará a Certbot que desea solicitar un nuevo certificado con los mismos dominios que un certificado existente. Ahora, la definición de servicio certbot tendrá este aspecto:

~/wordpress/docker-compose.yml
...
  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com
...

Ahora podrá ejecutar docker-compose para recrear el contenedor certbot. También incluiremos la opción --no-deps para indicar a Compose que puede omitir el inicio del servicio webserver, dado que ya está en ejecución:

  • docker-compose up --force-recreate --no-deps certbot

Verá un resultado que indicará que su solicitud de certificado fue exitosa:

Output
Recreating certbot ... done Attaching to certbot certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log certbot | Plugins selected: Authenticator webroot, Installer None certbot | Renewing an existing certificate certbot | Performing the following challenges: certbot | http-01 challenge for example.com certbot | http-01 challenge for www.example.com certbot | Using the webroot path /var/www/html for all unmatched domains. certbot | Waiting for verification... certbot | Cleaning up challenges certbot | IMPORTANT NOTES: certbot | - Congratulations! Your certificate and chain have been saved at: certbot | /etc/letsencrypt/live/example.com/fullchain.pem certbot | Your key file has been saved at: certbot | /etc/letsencrypt/live/example.com/privkey.pem certbot | Your cert will expire on 2019-08-08. To obtain a new or tweaked certbot | version of this certificate in the future, simply run certbot certbot | again. To non-interactively renew *all* of your certificates, run certbot | "certbot renew" certbot | - Your account credentials have been saved in your Certbot certbot | configuration directory at /etc/letsencrypt. You should make a certbot | secure backup of this folder now. This configuration directory will certbot | also contain certificates and private keys obtained by Certbot so certbot | making regular backups of this folder is ideal. certbot | - If you like Certbot, please consider supporting our work by: certbot | certbot | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate certbot | Donating to EFF: https://eff.org/donate-le certbot | certbot exited with code 0

Una vez implementados sus certificados, podrá modificar su configuración de Nginx para incluir SSL.

Paso 5: Modificar la configuración del servidor web y la definición del servicio

Habilitar SSL en nuestra configuración de Nginx implicará añadir un redireccionamiento HTTP a HTTPS especificando nuestras ubicaciones de certificados y claves SSL, y agregando parámetros y encabezados de seguridad.

Debido a que recreará el servicio webserver para incluir estas adiciones, puede detenerlo ahora:

  • docker-compose stop webserver

Antes de modificar el propio archivo de configuración, primero obtendremos los parámetros de seguridad de Nginx recomendados de Certbot usando curl:

  • curl -sSLo nginx-conf/options-ssl-nginx.conf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf

Con este comando se guardará estos parámetros en un archivo llamado options-ssl-nginx.conf, ubicado en el directorio nginx-conf.

A continuación, elimine el archivo de configuración de Nginx que creó anteriormente:

  • rm nginx-conf/nginx.conf

Abra otra versión del archivo:

  • nano nginx-conf/nginx.conf

Añada el siguiente código al archivo para redireccionar HTTP a HTTP y para agregar credenciales, protocolos y encabezados de seguridad SSL. Recuerde sustituir example.com por su propio dominio:

~/wordpress/nginx-conf/nginx.conf
server {
        listen 80;
        listen [::]:80;

        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;

        index index.php index.html index.htm;

        root /var/www/html;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        include /etc/nginx/conf.d/options-ssl-nginx.conf;

        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
        # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        # enable strict transport security only if you understand the implications

        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }

        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }

        location ~ /\.ht {
                deny all;
        }

        location = /favicon.ico {
                log_not_found off; access_log off;
        }
        location = /robots.txt {
                log_not_found off; access_log off; allow all;
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}

El bloque de servidor HTTP especifica el webroot para solicitudes de renovación de Certbot al directorio .well-known/acme-challenge. También incluye una directiva de reescritura que dirige las solicitudes HTTP al directorio root hacia HTTPS.

El bloque de servidor HTTPS habilita ssl y http2. Para obtener más información sobre la iteración de HTTP/2 en protocolos HTTP y los beneficios que puede tener para el rendimiento de sitios web, consulte la introducción de Cómo configurar Nginx con compatibilidad para HTTP/2 en Ubuntu 18.04.

En este bloque también se incluyen las ubicaciones de nuestros certificados y claves SSL, junto con los parámetros de seguridad de Certbot recomendados que guardamos en nginx-conf/options-ssl-nginx.conf.

Además, incluimos algunos encabezados de seguridad que nos permitirán obtener las calificaciones A en aspectos como los sitios de prueba de servidores de SSL Labs y Security Headers. Entre estos encabezados se incluyen X-frame-Options, X-Content-Type-Options, Referer Policy, Content-Security-Policy y X-XSS-Protection. El encabezado HTTP Strict Transport Security (HSTS) no se incluye: habilítelo solo si comprende las implicaciones y evaluó su funcionalidad de “precarga”.

Nuestras directivas root e index también se encuentran en este bloque, al igual que el resto de los bloques de ubicación específicos de WordPress analizados en el paso 1.

Una vez que finalice la edición, guarde y cierre el archivo.

Antes de recrear el servicio webserver, deberá añadir una asignación del puerto 443 a su definición de servicio webserver.

Abra su archivo docker-compose.yml:

  • nano docker-compose.yml

En la definición de servicio webserver, agregue la siguiente asignación de puerto:

~/wordpress/docker-compose.yml
...
  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

El archivo docker-compose.yml tendrá este aspecto al terminar la operación:

~/wordpress/docker-compose.yml
version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: wordpress:5.1.1-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge  

Guarde y cierre el archivo cuando concluya la edición.

Recree el servicio webserver:

  • docker-compose up -d --force-recreate --no-deps webserver

Compruebe sus servicios con docker-compose ps:

  • docker-compose ps

Debería ver un resultado que indique que sus servicios db, wordpress y webserver se encuentran en ejecución:

Output
Name Command State Ports ---------------------------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 db docker-entrypoint.sh --def ... Up 3306/tcp, 33060/tcp webserver nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp wordpress docker-entrypoint.sh php-fpm Up 9000/tcp

Una vez que sus contenedores se encuentren en ejecución, podrá completar su instalación de WordPress a través de la interfaz web.

Paso 6: Completar la instalación a través de la interfaz web

Al contar con nuestros contenedores en ejecución, podemos terminar la instalación a través de la interfaz web de WordPress.

En su navegador web, acceda al dominio de su servidor. Recuerde sustituir aquí example.com por su propio nombre de dominio:

https://example.com

Seleccione el idioma que desee utilizar:

Selector de lenguaje de WordPress

Después de hacer clic en** Continue**, accederá a la página de configuración principal, en la que deberá elegir un nombre para su sitio y un nombre de usuario. Se recomienda elegir aquí un nombre de usuario fácil de recordar (en lugar de “admin”) y una contraseña segura. Puede usar la contraseña que WordPress genera de manera automática o crear una propia.

Por último, deberá ingresar su dirección de correo electrónico y decidir si quiere evitar que los motores de búsqueda indexen su sitio:

Página principal de configuración de WordPress

Al hacer clic sobre Install WordPress en la parte inferior de la página, accederá a una solicitud de inicio de sesión:

Pantalla de inicio de sesión de WordPress

Una vez que inicie sesión, tendrá acceso al panel de administración de WordPress:

Panel principal de administración de WordPress

Una vez completada su instalación de WordPress, podrá tomar medidas para asegurarse de que sus certificados SSL se renueven de forma automática.

Paso 7: Renovar certificados

Los certificados de Let’s Encrypt son válidos durante 90 días, por lo que le convendrá configurar un proceso de renovación automática para asegurarse de que no caduquen. Una forma de hacerlo es crear una tarea con la utilidad de programación cron. En este caso, crearemos una tarea cron para que de forma periódica ejecute una secuencia de comandos que renovará nuestros certificados y volverá a cargar nuestra configuración de Nginx.

Primero, abra una secuencia de comandos llamada ssl_renew.sh:

  • nano ssl_renew.sh

Agregue el siguiente código a la secuencia de comandos para renovar sus certificados y volver a cargar la configuración de su servidor web. No se olvide de sustituir el nombre de usuario de ejemplo por su nombre de usuario no root:

~/wordpress/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Esta secuencia de comandos primero asigna el binario docker-compose a una variable llamada COMPOSE y especifica la opción --no-ansi, que ejecutará comandos docker-compose sin los caracteres de control ANSI. Luego hace lo mismo con el binario docker. Luego, cambia el posicionamiento al directorio del proyecto ~/wordpress y ejecuta los siguientes comandos docker-compose:

  • docker-compose run: iniciará un contenedor certbot y anulará el command proporcionado en nuestra definición de servicio certbot. En lugar de usar el subcomando de certonly, usaremos aquí el subcomando renew que renovará certificados que caducarán pronto. En este caso, incluimos la opción --dry-run para probar nuestra secuencia de comandos.
  • docker-compose kill: enviará una señal de SIGHUP al contenedor webserver para volver a cargar la configuración de Nginx. Si desea obtener más información sobre el uso de este proceso para volver a cargar su configuración de Nginx, consulte este post del blog de Docker sobre la implementación de la imagen oficial de Nginx con Docker.

Luego ejecuta docker system prune para eliminar todos los contenedores y las imágenes que no se utilizan.

Cierre el archivo cuando finalice la edición. Haga que sea ejecutable:

  • chmod +x ssl_renew.sh

A continuación, abra su archivo root crontab para ejecutar la secuencia de comandos de renovación en un intervalo especificado:

  • sudo crontab -e

Si es la primera vez que edita este archivo, se le solicitará elegir un editor:

Output
no crontab for root - using an empty one Select an editor. To change later, run 'select-editor'. 1. /bin/nano <---- easiest 2. /usr/bin/vim.basic 3. /usr/bin/vim.tiny 4. /bin/ed Choose 1-4 [1]: ...

Al final del archivo, añada la siguiente línea:

crontab
...
*/5 * * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

Esto fijará un intervalo de tarea de cinco minutos, de modo que puede probar si su solicitud de renovación ha funcionado como estaba previsto. También creamos un archivo de registro, cron.log para registrar el resultado pertinente de la tarea.

Una vez que transcurran cinco minutos, revise ​​​​​cron.log​​​​​​ para comprobar si la solicitud de renovación se realizó con éxito o no:

  • tail -f /var/log/cron.log

Debería ver un resultado que confirme el éxito de la renovación:

Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/example.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Ahora podrá modificar el archivo de crontab para establecer un intervalo diario. Para ejecutar la secuencia de comandos cada día al mediodía, por ejemplo, se debería modificar la última línea del archivo de modo que tenga el siguiente aspecto:

crontab
...
0 12 * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

También le convendrá eliminar la opción --dry-run de su secuencia de comandos ssl_renew.sh:

~/wordpress/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Su tarea cron controlará que sus certificados de Let´s Encrypt no caduquen al renovarlos cuando reúnan las condiciones. También puede configurar la rotación de registros con la utilidad Logrotate para rotar y comprimir sus archivos de registro.

Conclusión

A través de este tutorial, utilizó Docker Compose para crear una instalación de WordPress con un servidor web de Nginx. Como parte de este flujo de trabajo, obtuvo certificados TLS y SSL para el dominio que quiere asociar con su sitio de WordPress. Además, creó una tarea cron para renovar estos certificados cuando sea necesario.

Como pasos adicionales para mejorar el rendimiento y la redundancia del sitio, puede consultar los siguientes artículos sobre la entrega y el respaldo de activos de WordPress:

Si está interesado en explorar un flujo de trabajo en contenedor con Kubernetes, también puede consultar lo siguiente:

Creative Commons License