Docker and WordPress
I run a couple of WordPress websites. For the many years I’ve been using various shared web hosts to host the sites, but recently they started to fail me for various reasons. Fortunately, I use a great backup solution, so moving to another host is easy.
I decided to get myself a Virtual Private Server, or Virtual Machine, to host the websites myself. This would give me full control of how they were run, but I would be responsible for ensuring they were stable. I decided to use Docker to containerise the whole thing and make sure it was portable to another machine if I required it later.
Side note: I’ve signed up with BuyVM. They are very reasonably priced and so far are very reliable. I have previously used their shared hosting (which was very stable), but due to price changes with cPanel it worked out more efficient for me to get a VPS.
I signed up for the VPS and applied a Debian 9.0 64bit image. I booted up, installed Docker and some basic things like UFW firewall and then got started.
Overview
So how will this work?
- Traffic from the internet hits the Proxy. Only the Proxy is exposed to the internet. Everything else is only accessible internally. The proxy is running the nginx-proxy Docker container.
- The Proxy has a helper that speaks to LetsEncrypt to manage generating HTTPS certificates.
- The proxy determines which VM to send the request to. It does this by inspecting environment variables
VIRTUAL_HOST
andLETSENCRYPT_HOST
. - The appropriate WordPress container handles the request. Each WordPress container is running it’s own Apache server and is configured to speak to it’s own database container (running MariaDB).
Proxying Traffic
I want to host multiple websites from the same VPS. To be able to handle multiple different hostnames, I needed to use nginx-proxy
to handle the incoming traffic and redirect it to the appropriate WordPress Docker instance. I also want to serve traffic over HTTPS, so I’m using jrcs/letsencrypt-nginx-proxy-companion
to inspect the contents of nginx-proxy and request the appropriate certificates from LetsEncrypt.
The nginx-proxy
instance observes the main Docker socket, so there is almost zero configuration required to add back-end instances. If I want to add other back-end services that I need to be public, not just WordPress, I can use the same setup to simply add new containers and everything works magically.
Everything goes together in a Docker Compose file:
version: "3"
services:
proxy:
image: jwilder/nginx-proxy
container_name: proxy
ports:
- "80:80"
- "443:443"
volumes:
- certs:/etc/nginx/certs:ro
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
- /var/run/docker.sock:/tmp/docker.sock:ro
labels:
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
https_proxy:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: https_proxy
volumes:
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- certs:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- "proxy"
volumes:
certs:
vhost:
html:
dhparam:
networks:
default:
external:
name: nginx-proxy
Running WordPress and MariaDB
I’m using the official WordPress Docker image. This runs a full Apache / PHP stack and latest version of WordPress. I’ve also decided to give each WordPress instance it’s own database instance. There is a bit of overhead running multiple database servers simultaneously, but I feel this is outweighed by the redundancy and simplicity of the stack.
version: '3.1'
services:
cjc_wp:
depends_on:
- db
image: wordpress
restart: always
expose:
- 80
links:
- db:mysql
environment:
VIRTUAL_HOST: ceejaycee.net, www.ceejaycee.net
LETSENCRYPT_HOST: ceejaycee.net, www.ceejaycee.net
WORDPRESS_DB_PASSWORD: 'MYSECRETPASSWORD'
WORDPRESS_DB_NAME: wp
container_name: cjc_wp
volumes:
- /home/chris/config/data/wp:/var/www/html:rw
- /home/chris/config/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
db:
image: mariadb
restart: always
environment:
MYSQL_ROOT_PASSWORD: 'MYSECRETPASSWORD'
container_name: db
volumes:
- /home/chris/config/data/db:/var/lib/mysql:rw
networks:
default:
external:
name: nginx-proxy
We have created volumes for the Apache www folder. This allows it to be persistent across sessions and allows us to modify the content easily, if required. We also map in an uploads.ini
file, which allows us to override the default upload file size:
file_uploads = On
memory_limit = 512M
upload_max_filesize = 512M
post_max_size = 512M
max_execution_time = 600
Setting Up Each Site
I have all the individual WordPress instance content, plugins, themes and configuration backed up to Dropbox. There are two approaches to get each site up and running:
- Go to Dropbox, download the last backup. Copy the files to the Docker container. Then restore the database.
- Follow the WordPress install wizard. Install the UpdraftPlus plugin, authenticate with Dropbox, click “Restore”.
I usually follow step 2, because it’s much easier.
Backing up the Configuration
To ensure this configuration is reproducible and portable to other systems, it’s important to ensure the configuration is stored and backed up. I use a private GitHub repository to store the configuration. If I need to move this setup to another system, I can simply git clone the configuration files to another system, point the DNS to a new box, and it’s ready to go.
The important WordPress data (content, images, database, individual site configuration) is automatically backed up to Dropbox every night using a WordPress plugin.
And that’s all there is to it…
Note: some of the links on this post are affiliate links. If you sign up to use these services, I may get a small referral fee which will help keep this site alive.