JustJeff

Più Wordpress con Docker

Quando sei stufo di gestire molte istanze Wordpress e hai del tempo libero

A steampunk wordpress logo

Partiamo dal principio.

A tutti è capitato di installare la propria istanza di Wordpress in locale per divertimento o curiosità.

A molti è capitato di dover comprare un servizio di hosting Wordpress gestito, in modo per lo più scadente, per il proprio cuggino[^Consiglio non richiesto: mai trasformare il cuggino in cliente.] che vuole pubblicare i suoi articoli su Punto croce e Uncinetto

A quanto basta è capitato di dover iniziare a gestire più installazioni di Wordpress per gli amici del cuggino, tutti inspiegabilmente appassionati di tecnica di ricamo.

A pochi è capitato di dover installare e configurare più istanze di Wordpress, per clienti diversi, su una VPS o VDS, gestendo sia lo sbatti 1 dell’installazione, sia i clienti. Doppio lavoro.

Dato che a me le visualizzazioni non piacciono2, questo articolo si concentra su questo ultimo gruppo ristretto: i pochi.

Il problema

Voi pochi sapete benissimo che installare Wordpress, per quanto non sia rocket science, rimane una discreta menata tra webserver, MySql, Wordpress e la loro configurazione.

Per non parlare poi se è necessario aggiungere una nuova istanza. Non ce lo mettiamo un reverse proxy? Per non parlare poi se è necessario isolare i DB. Per non parlare poi se bisogna usare HTTPS. Per non parlare poi… ecco, non parliamone.

Le malelingue a questo punto diranno:
si, vabbeh, ma io mi sono scriptato tutto, ho NGINX installato sulla macchina, mi copio e incollo le folder di Wordpress, lancio a mano certbot.

Io al loro punto risponderò:
sì vabbeh, anche io facevo così. Poi camminando davanti ad uno specchio mi sono accorto di non essere una scimmia 3

La soluzione

Alt text

Quello che faremo è utilizzare Docker Compose per eseguire e gestire tutti i nostri container:

  • NGINX Reverse-Proxy: di nome, di fatto;
  • ACME-COMPANION: per gestire i certificati SSL;
  • MariaDB: come database Wordpress, uno per istanza Wordpress
  • Wordpress: come Wordpress

R2, R2, L1, L1, Sinistra, Giù, Destra, Su, Sinistra, Giù, Destra, Su

Nel caso tu voglia barare e avere tutto pronto, eccoti il repo git: https://github.com/Jeffr89/manywp-nginx-dockercompose

Prerequisiti

Iniziamo dal dire che bisogna conoscere:

  • qualcosina di Linux,
  • qualcosa di Docker
  • qualcosa di più di Docker Compose

Sarebbe utile avere anche:

  • un server Linux esposto su internet, con il suo bell’indirizzo IP
  • un meraviglioso dominio che dovrà puntare all’IP del nostro coccolato server Linux
  • Docker e Docker Compose installati sul sopra citato meraviglioso server Linux.

Nel caso non abbiate nulla di tutto ciò, dato che sono magnanimo e vi voglio bene per non avere utilizzato i trucchi per ottenere i soldi facili4, questa guida si potrà utilizzare anche in locale5, a scopo di test, con alcuni accorgimenti che vedremo in seguito.

Let’s start!

In primis creiamo ed entriamo nella folder che conterrà il nostro progetto: manywp

Terminal window
mkdir manywp
cd manywp

Creiamo anche due folder: nginx-config e wp-config che conterranno i file di configurazione per nginx e Wordpress.

Terminal window
mkdir nginx-config
mkdir wp-config

Ultimiamo il nostro scheletro creando anche i file di configurazione che vedremo nei prossimi capitoli:

  • docker-compose.yml: che conterrà il setup del nostro ambiente dockerizzato
  • my_proxy.conf: che conterrà le configurazioni del proxy NGINX
  • uploads.ini: che conterrà le configurazioni relative a PHP
Terminal window
touch docker-compose.yml
touch nginx-config/my_proxy.conf
touch wp-config/uploads.ini

A questo punto la nostra folder di progetto sarà completa:

Alt text

my_proxy.conf

Utilizzando il vostro editor preferito, andiamo a popolare il file contenente le configurazioni che ‌daremo in pasto ad NGINX:

Terminal window
client_max_body_size 512m;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;

Se siete curiosi di sapere a cosa servono vi lascio il link a tutte le direttive di NGINX: https://nginx.org/en/docs/dirindex.html

uploads.ini

Anche in questo caso andiamo a popolare il file di configurazione per evitare quei fastidiosi errori di maximum uploads size e compagnia bella di Wordpress:

Terminal window
file_uploads = On
memory_limit = 256M
upload_max_filesize = 75M
post_max_size = 75M
max_execution_time = 600

Docker-Compose.yml

Finalmente arriviamo alla ciccia.

Per facilità ho deciso di raggruppare tutta la configurazione in un’unico file docker-compose.yml.
In base alle esigenze è anche possibile dividere i service all’interno di diversi file, utile nel caso abbiate molte istanze Wordpress e non volete tenere tutte le variabili d’ambiente in un unico file.

Ho anche preferito non inserire nessun dato sensibile all’interno del docker-compose.yml, preferendo creare un file .env contenente tutte le variabili d’ambiente necessarie.

Bando alle ciance, iniziamo a vedere uno ad uno i services che costituiranno la nostra creatura.

Il primo ospite: Nginx Reverse Proxy

Il service nginxproxy/nginx-proxy si occupa di dirigere automaticamente il traffico ai diversi container in base alla variabile d’ambiente VIRTUAL_HOST associata ad ogni container

nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx-config/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf
- certs:/etc/nginx/certs
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
networks:
- net

In questo caso siamo andati a copiare il file di configurazione come volume del container.

La spalla: Acme Companion

Il service nginxproxy/acme-companion si occupa di tutta la stregoneria dietro all’ottenere i certificati SSL per permettere l’uso di HTTPS per tutti i nostri domini.

nginx-proxy-acme:
depends_on:
- "nginx-proxy"
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- certs:/etc/nginx/certs
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- acme:/etc/acme.sh
environment:
- DEFAULT_EMAIL=${DEFAULT_EMAIL}
- NGINX_PROXY_CONTAINER=nginx-proxy
networks:
- net

Come avrete notato, o forse no?!, sarà necessario valorizzare la variabile DEFAULT_EMAIL con una mail valida per ottenere eventuali notifiche o alert sui certificati. È una variabile opzionale, ma caldamente consigliata.

La First Lady: MariaDB

Che dire? Poteva essere anche MySql, ma mi ricorda una delle mie canzoni preferite, quindi Maria[^Gloria a Santana] sia.

db_wp_00:
image: mariadb:latest
volumes:
- db_wp_00_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${DB_WP_00_PASSWORD}
MYSQL_DATABASE: ${DB_WP_00_DATABASE}
MYSQL_USER: ${DB_WP_00_USER}
MYSQL_PASSWORD: ${DB_WP_00_PASSWORD}
container_name: db_wp_00
networks:
- wp_00

Anche in questo caso abbiamo alcune variabili da impostare. I nomi sono particolarmente comprensibili.
Questo service potrà essere ripetuto per ogni istanza di Wordpress di cui abbiamo bisogno, ovviamente stando attendi a modificare i valori DB_WP_## in modo che coincidano con i container Wordpress.

In questo modo il volumes persisterà in caso di interruzione/riavvio/aggiornamento del container, cosi da non perdere i dati.

Il presidente: Wordpress

Ultimo ma non meno importante, anzi, il motivo per cui siamo qui.

wp_site_00:
depends_on:
- db_wp_00
image: wordpress:6.0-php8.0-apache
container_name: wp_site_00
restart: always
environment:
VIRTUAL_HOST: ${WP_SITE_00_VIRTUAL_HOST}
LETSENCRYPT_HOST: ${WP_SITE_00_LETSENCRYPT_HOST}
WORDPRESS_DB_HOST: ${WP_SITE_00_DB_HOST}
WORDPRESS_DB_USER: ${DB_WP_00_USER}
WORDPRESS_DB_PASSWORD: ${DB_WP_00_PASSWORD}
WORDPRESS_DB_NAME: ${DB_WP_00_DATABASE}
WORDPRESS_TABLE_PREFIX: ${WP_SITE_00_TABLE_PREFIX}
volumes:
- wp_site_00_data:/var/www/html
- *phpini-volume
networks:
- wp_00
- net

Le note relative al service MariaDB valgono anche in questo caso ma si aggiungono un paio di casette importanti.

La prima: due variabili d’ambiente, LETSENCRYPT_HOST e WORDPRESS_DB_HOST che andranno valorizzate con il dominio di riferimento.\

NB:
Se siete in locale potete valorizzarle con 127.0.0.1 in modo da poter accedere all’istanza Wordpress a fini di test.
Se invece volete testare più istanze potete valorizzare le due variabili con dei domini inventati (e.g. wordpressone.com)
Ricordate di aggiungere i domini al file /etc/hosts:
e.g. 127.0.0.1 wordpressone.com

La seconda: un esoterico *phpini-volume tra i volumi.
Dato che per ogni container Wordpress è necessario montare il file di configurazione uploads.ini ho preferito creare un extension-field in modo da non rischiare refusi:

x-services-volume:
&phpini-volume
type: bind
source: ./wp-config/uploads.ini
target: /usr/local/etc/php/conf.d/uploads.ini

Volumes e Network

Chiudiamo il nostro docker-compose.yml con la parte relativa ai volumi, che verranno persistiti da docker, e al network, che prevede che il db_wp_00 possa comunicare esclusivamente con il suo papà wp_site_00:

volumes:
certs:
vhost:
html:
dhparam:
acme:
db_wp_00_data:
wp_site_00_data:
networks:
wp_00:
internal: true
net:
driver: bridge

Uniamo i puntini

Eccome come dovrebbe apparire il nostro docker-compose.yml in tutto il suo splendore:

x-services-volume:
&phpini-volume
type: volume
source: ./wp-config/uploads.ini
target: /usr/local/etc/php/conf.d/uploads.ini
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx-config/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf
- certs:/etc/nginx/certs
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
networks:
- net
nginx-proxy-acme:
depends_on:
- "nginx-proxy"
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- certs:/etc/nginx/certs
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- acme:/etc/acme.sh
environment:
- DEFAULT_EMAIL=${DEFAULT_EMAIL}
- NGINX_PROXY_CONTAINER=nginx-proxy
networks:
- net
db_wp_00:
image: mariadb:latest
volumes:
- db_wp_00_data:/var/lib/mysql
container_name: db_wp_00
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${DB_WP_00_PASSWORD}
MYSQL_DATABASE: ${DB_WP_00_DATABASE}
MYSQL_USER: ${DB_WP_00_USER}
MYSQL_PASSWORD: ${DB_WP_00_PASSWORD}
networks:
- wp_00
wp_site_00:
depends_on:
- db_wp_00
image: wordpress:6.0-php8.0-apache
container_name: wp_site_00
restart: always
environment:
VIRTUAL_HOST: ${WP_SITE_00_VIRTUAL_HOST}
LETSENCRYPT_HOST: ${WP_SITE_00_LETSENCRYPT_HOST}
WORDPRESS_DB_HOST: ${WP_SITE_00_DB_HOST}
WORDPRESS_DB_USER: ${DB_WP_00_USER}
WORDPRESS_DB_PASSWORD: ${DB_WP_00_PASSWORD}
WORDPRESS_DB_NAME: ${DB_WP_00_DATABASE}
WORDPRESS_TABLE_PREFIX: ${WP_SITE_00_TABLE_PREFIX}
volumes:
- wp_site_00_data:/var/www/html
- *phpini-volume
networks:
- wp_00
- net
volumes:
certs:
vhost:
html:
dhparam:
acme:
db_wp_00_data:
wp_site_00_data:
networks:
wp_00:
internal: true
net:
driver: bridge

Segretissimo .env

Qua la scelta spetta a voi. Io ho preferito creare un file .env, che dove essere nella folder principale di progetto, per contenere tutte le variabili d’ambiente necessarie ai diversi container, ma potete tranquillamente valorizzarle all’interno del docker-compose.yml

#Let's Encrypts email alert
#db_wp_00 enviroment variables
DB_WP_00_ROOT_PASSWORD=YourRootPassword
DB_WP_00_DATABASE=mysqldatabasename
DB_WP_00_USER=mysqlusername
DB_WP_00_PASSWORD=mySqlUserPassword
#wp_site_00 enviroment variables
WP_SITE_00_VIRTUAL_HOST=www.yourdomain.com, your.domain.com
WP_SITE_00_LETSENCRYPT_HOST=www.yourdomain.com, yourdomain.com
WP_SITE_00_DB_HOST=db_wp_00
WP_SITE_00_TABLE_PREFIX=customprefix

Show down!

È il momento di scoprire le carte.
Andiamo nella nostra folder principale e facciamo l’incantesimo:

Terminal window
Docker-compose up -d

Dopo un po’ di attesa i nostri container saranno in stato started

Alt text

Non ci resta che aprire il browser e provare a raggiungere il nostro dominio o casa:

Alt text

Senza troppa fatica siamo riusciti a deployare una bella soluzione con un’istanza di Wordpress e, a questo punto, con ancor meno fatica potremmo aggiungerne quante ne vogliamo.

Lascio a voi occuparvi di fare i backup dei volumi dei container. Non posso fare tutto io. Almeno non in un solo articolo.
A tal proposito fate molta attenzione a non eseguire il comando docker-compose down con l’opzione --volumes6

Sipario!

Footnotes

  1. Consiglio non richiesto: mai trasformare il cuggino in cliente.

  2. Cosa vorrà mai dire?

  3. Almeno non quella giusta

  4. Scusate se sono rimasto a GTA3

  5. There’s No Place Like 127.0.0.1

  6. Gravi conseguenze