Più Wordpress con Docker
Quando sei stufo di gestire molte istanze Wordpress e hai del tempo libero
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
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
mkdir manywp cd manywpCreiamo anche due folder: nginx-config e wp-config che conterranno i file di configurazione per nginx e Wordpress.
mkdir nginx-config mkdir wp-configUltimiamo il nostro scheletro creando anche i file di configurazione che vedremo nei prossimi capitoli:
docker-compose.yml: che conterrà il setup del nostro ambiente dockerizzatomy_proxy.conf: che conterrà le configurazioni del proxy NGINXuploads.ini: che conterrà le configurazioni relative a PHP
touch docker-compose.yml touch nginx-config/my_proxy.conf touch wp-config/uploads.iniA questo punto la nostra folder di progetto sarà completa:
my_proxy.conf
Utilizzando il vostro editor preferito, andiamo a popolare il file contenente le configurazioni che daremo in pasto ad NGINX:
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:
file_uploads = On memory_limit = 256M upload_max_filesize = 75M post_max_size = 75M max_execution_time = 600Docker-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: - netIn 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: - netCome 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_00Anche 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 - netLe 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.iniVolumes 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: bridgeUniamo 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: bridgeSegretissimo .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 variablesDB_WP_00_ROOT_PASSWORD=YourRootPasswordDB_WP_00_DATABASE=mysqldatabasenameDB_WP_00_USER=mysqlusernameDB_WP_00_PASSWORD=mySqlUserPassword
#wp_site_00 enviroment variablesWP_SITE_00_VIRTUAL_HOST=www.yourdomain.com, your.domain.comWP_SITE_00_LETSENCRYPT_HOST=www.yourdomain.com, yourdomain.comWP_SITE_00_DB_HOST=db_wp_00WP_SITE_00_TABLE_PREFIX=customprefixShow down!
È il momento di scoprire le carte.
Andiamo nella nostra folder principale e facciamo l’incantesimo:
Docker-compose up -dDopo un po’ di attesa i nostri container saranno in stato started
Non ci resta che aprire il browser e provare a raggiungere il nostro dominio o casa:
Epilogo
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!