18 April, 2018

The start of summer ft. Wordpress on Docker

The first day of warm spring weather in London is always one of mixed emotions for me. I'm something of a fair weather sportsman and cruising over Waterloo Bridge in shorts and a t-shirt is a welcome change from the damp leggings and luminous hi-vis that is winter riding in the UK. On the other hand, the dam finally gives way and a flood of cyclists appear back on the roads, bleary-eyed from hibernation and ready to cause mayhem.

Without question, this is something for celebration. The more people that cycle, the stronger the impetus for investment in cycling infrastructure and the greater the number of people that experience the benefits of consistent exercise. Additionally, cycling as a sport has a reputation for being rude and unwelcoming to the casual rider (my own run-ins with furious and hysterical lycra-clad commuters who think they're competing for the yellow jersey are testament to that) and it's nice to see that people aren't being put off.

Having said that, it does lead to a few days of carnage as everyone gets their legs back. Weaving my way home, I watched the madness unfold as the spring chickens changed lanes with abandon, used the pavements as cycle lanes and generally made the most of their first commute of the season. As I said, it's great to see more people than ever taking to the road on two wheels but it makes for an exciting few days.

Running WordPress on Docker

Anyway, on to today's main and unrelated topic, a brief overview of running Wordpress on Docker (What else is there to do on a nice sunny day other than write wonky blog posts?). The official Docker Wordpress page gets you most of the way and is by far the easiest way that I've found to get Wordpress up and running. Unfortunately, the setup described on the Docker website isn't quite complete and at minimum, an additional volume is needed to persist the Wordpress data directory. If the data directory isn't persisted, media, themes and other settings won't get persisted which isn't very useful if you're trying to run a blog.

As an added extra, I've set up an Nginx reverse proxy to manage access to the WordPress container which also includes some request limiting protection to the login page. Bear in mind that my Nginx knowledge is pretty basic so whilst it's functional, I'm sure there are better ways to achieve the same thing.

My docker-compose.yml:

version: '3.6'
 
services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: db_root_pass
       MYSQL_DATABASE: db
       MYSQL_USER: db_user
       MYSQL_PASSWORD: db_pass
   nginx:
     depends_on:
       - wordpress
     image: nginx:latest
     restart: always
     ports:
       - "80:80"
     volumes:
       - ./nginx.conf:/etc/nginx/nginx.conf
   wordpress:
     depends_on:
       - db
     volumes:
       - wp_data:/var/www/html/wp-content
     image: wordpress:latest
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: db_user
       WORDPRESS_DB_PASSWORD: db_pass
volumes:
   db_data:
   wp_data:

What's going on here then is that I've lightly modified the steup provided with the Docker tutorial, added an extra volume for the wp-content folder and included an Nginx container, which exposes port 80.

and my nginx.conf:

http {
 
    client_body_timeout 5s;
    client_header_timeout 5s;
 
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
    limit_req_zone $binary_remote_addr zone=sitewide:10m rate=120r/m;
 
    server {
         
        listen 80 default_server;
     
    location /wp-admin {
 
        proxy_pass http://wordpress;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
    }
 
        location / {
 
            proxy_pass http://wordpress;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
           limit_conn addr 5;
           limit_req zone=sitewide burst=30 nodelay;           
 
           location /wp-login.php {
 
                proxy_pass http://wordpress;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;           
 
               limit_req zone=login burst=3 nodelay;
 
           }           
 
        }
    }
}

Here, I'm configuring the reverse proxy to proxy requests from the Nginx container to the WordPress container, setting up a listener on container port 80 (which is then bound to port 80 on the host) and adding some rate and timeout limiting to both the login page and to the rest of the site. This should prevent brute-force attacks against the login page and also stop DDoS-style attacks by returning a 503 if you spam any of the pages on the blog.

© 2023 Henry Course