Docker Wordpress Nginx



I am going to build a dual Wordpress stack with Traefik, Nginx, PHP-fpm, and MariaDB in Docker, using Docker-compose. I plan to eventually migrate into this from an older server I have with locally installed Wordpress.

Log in to the first Nginx container with docker-compose command. Docker-compose exec nginx bash. Nginx: service name on the docker-compose file docker-compose.yml. Bash: execute the bash shell command. Now check our WordPress virtual host configuration. Cat /etc/nginx/conf.d/wordpress.conf. The files are available in the container. Use Docker to Create Multiple Websites on One Server Your backend website can be anything from a WordPress blog to a custom web app written by you. As long as your app is packaged into a Docker image, its fair game. In this video we’ll take a look at installing WordPress on Docker! WordPress is open source software you can use to create a beautiful website, blog, or app. In order to setup a WordPress container on your Docker server, you’ll need a few additional things.

I didn't find a lot of examples on the web with Traefik feeding Nginx in a chain to other services, so I've written one. Bring on the mega-post.

My initial strategic move would have been to migrate away from Wordpress altogether to something more streamlined like Ghost, but I don't really like the way it handles static sites yet, and with no native comment system I would need migration work. So, I will rehost with Wordpress and defer a more serious replatform until later.

Architecture of Docker - Traefik - Nginx - Wordpress - MariaDB

I am opting to separate out the whole component stack, although in theory the same Nginx and database instances could be shared. I have come across problems with multiple Wordpress instances sharing the same database in the past, so I am going to spend the extra 70-80MB of memory for an additional database. You could of course tune this configuration to share databases.

Docker wordpress nginx letsencrypt

Why retain Nginx I hear you ask, when most default to the bundled Apache build? After some tinkering with Traefik, I did not see an elegant way in Traefik to implement one of my favourite features - IP whitelisting of the wp-admin and indeed the whole login system under wp-login. The bot armies of the world continuously poke open WP login pages, and as I have no need for anyone else to get to the admin panels I am happy to manage an IP whitelist. I achieve that today by SSHing in to the server and editing an ipfilter.conf file which I have set up for Nginx.

Also, FastCGI is better than a poke in the eye. It's also possible that Nginx is simply faster than Apache. Whatever, you might have your own reasons to want Nginx.

So I end up with Traefik doing what it does best, as a Let's Encrypt SSL automator, and front end traffic broker for my site.

The VPS server build

As part of this I will be shifting from a Digital Ocean VPS to an AWS Lightsail one, and downgrading from 2GB memory to 1GB. I believe the DO instances are definitely better performers for the same $, my move is due to an unrelated financial credit I have on AWS resources. Regardless, once containerised, a move to a new provider is not a lot more than shifting the /data tree.

Docker Wordpress Nginx Php-fpm

I won't dwell too much on the initial server build and seed with basic Docker and Compose as I have already covered a similar process here. The main difference to that model being that I will drop Ghost and add the other containers.

Fast forward to a Lightsail Ubuntu 18.04 server with Traefik running. My docker-compose.yml looks like this. Names are always changed for anonymity.

At this point you should be able to see the Traefik dashboard on https://monitor.yourhost.com .

Add the Nginx containers

Nginx container structure

Nginx will receive traffic from Traefik, so will need to be in the web network, for internet facing. Traefik will receive HTTPS internet traffic, terminate SSL, and use unencrypted HTTP ports to contact the Nginx containers. Docker will map the inbound port to Nginx port 80 so both of our Nginx configurations can listen on that.

Docker Wordpress Nginx

Docker compose example - Traefik forwarding to two Nginx servers

There are many examples around of nginx configurations using labels like traefik.port=8081 . I would not recommend using this, I got caught up for hours with the dreaded 'bad gateway' until I realised the ports can be dynamically managed and removed it. YMMV.

Note: I have gone with the Traefik image 'traefik:maroilles-alpine' as I am trying to get the latest version 1 Traefik - alpine combo, without risk of it automatically updating to version 2. I like to live on the edge and run a script in cron to auto upgrade within major point releases, this is optional. It's probably no problem to just use the straight up 'traefik' image, but this article is written for V1.

Visiting the frontend URLs such as yourblog1.com should reward you with the default Nginx screen. And because this is Traefik, HTTPS should be taken care of and no longer be a concern - other than contributing to the Let's Encrypt initiative, should you want to pay it forward.

Taking a snapshot of the current resource usage is heartening, with the Nginx containers taking only 2MB each.

So let's continue. Nginx will talk to the Wordpress-fpm container, so it will also need to be in the internal network. Nginx will talk to it on port 9000, but this is not via docker compose, it is from within the application. We just need to ensure the Nginx and Wordpress containers are in the same network.

Nginx will need to have access to the wordpress html, php and config files, so will need the webroot volume mapped.

Nginx data directories

We will need an area on disk for the Nginx configuration files to be mounted to the container. I use /data for my stuff, this is arbitrary. Let's make three data directories, /data/nginx-wp1, /data/logs/nginx-wp1, and /data/wp1 for the wordpress web files, and another three for the second stack.

Eventually we will map them all in the 'volumes' section. Examples later.

Our second stack will need similar directories, changing all the wp1's to wp2's, i.e.

We can't go a lot further incrementally without standing up a new vanilla WP site, so let's do that.

Important note on logs - it's not ideal to log into the data directories. I improve this in this post. https://techroads.org/docker-logging-to-the-local-os-that-works-with-compose-and-rsyslog/

Nginx starter config file

After creating the directories, an interim and minimal nginx config file that will communicate with Wordpress, called say 'default.conf' is:

There are four things to change. The server entry, the two log file names, and the WP/PHP target.

The two log file entries refer to paths inside the container which we later map. These should stay as /var/log/nginx, but the file name prefixes can be changed to be meaningful for your blog name.

Note the fastcgi_pass wp1:9000; entry. The wp1:9000 part is the target address for our wordpress-fpm container. When we define the wordpress container it is essential that we make the container name wp1, so it can be reached over the internal network. For our second leg we will need a version of this config file referring to wp2:9000 and a Wordpress container wp2.

Add the WP1 conf file to /data/nginx-wp1 and the WP2 conf file to /data/nginx-wp2.

Note that initially I did not have a server_name at all, but I found there were problems with 'too many redirects', somehow Traefik was getting confused over where to deliver traffic. Since I've added server_names, it's been pretty solid.

Add the Database containers

Wordpress

I want a separate database for each of my Worpdress 'stacks'. You can use MySQL or MariaDB (MySQL compatible) images for the Wordpress database. I am going with MariaDB. You can read about it on the Docker Hub page here: https://hub.docker.com/_/mariadb

Create /data/mariadb1 and /data/mariadb2 directories. These can remain empty and will be populated with data files on first run.

Passwords in the compose file

  • MYSQL_ROOT_PASSWORD is the database administrator password.
  • MYSQL_USER and MYSQL_PASSWORD will create the Wordpress application user, assign a password, and grant admin privileges when the database container is instantiated.
  • WORDPRESS_DB_USER and WORDPRESS_DB_PASSWORD are the values given to the Wordpress instance so that it can connect to the database. These must match the application user credentials.

Optionally, you could instead manually create the Wordpress application user and grant privileges, but that is not covered here.

You could also move all the passwords to external config files.

MYSQL_ROOT_PASSWORD should be set both in your local environment from the command line, and at the end of your .profile file. A syntax example:

export MYSQL_ROOT_PASSWORD=secure-pass-123

Of course, substitute your own password. Setting it this way in the environment variable means it can be left blank in your docker compose, as shown. It will be set on first run. If you want the DBs to have different root passwords, you'll need a layer of abstraction which I am not covering here.

I want the latest within the major 10 release, so will use the image mariadb:10. To cut to the chase - here is my DB config.

Docker Compose example - MariaDB for Wordpress

In case you haven't put it together yet, the service name mariadb1 & 2 seen here is also the network name, which Wordpress will need to reach the database over the Docker internal network.

Add the Wordpress containers

Likewise our Wordpress container will need service names of wp1 and wp2 in order that Nginx processes can reach them over the internal network.

If you didn't already create /data/wp1 and /data/wp2 directories during the Nginx setup, do it before proceeding. These can remain empty and will be populated with data files on first run.

Nginx

My web server user is www-data, numbered 33 in the /etc/passwd file, so I will add it here to make sure the instance has permissions.

Docker compose example - Wordpress with FPM for Nginx

Fire up the 'wp1' stack

Uncomment or add the internal network at the top of the compose file, the networks section should end up looking like this. This name is arbitrary.

The full docker-compose.yml is below. Your checklist:

  • Replaced all URLs with your URLs
  • Replaced all /data paths with your data paths, if they are different
  • WORDPRESS_DB_HOST is set to the respective database service names
  • Each .conf file you have copied into the nginx data directories has the four values updated including for wp1:9000 and wp2:9000 respectively
  • MYSQL_ROOT_PASSWORD is set, can be checked with the shell command env | grep MYSQL_ROOT_PASSWORD

Docker compose example - the full config for a dual Wordpress Nginx stack

This is straight from my working system, with just the domain names and password fields changed.

Docker

The tasty reward on each of your blog URLs:

Performance profile of container based wordpress

Server-side the total memory footprint of the containers for my twin-stack at this stage seems to be around 210MB. The total server usage is presently 390MB. In theory you could add probably another four stacks the same of lightly loaded blogs and still run ok within a 1GB VPS, but it's unpredictable given the bloatware that Wordpress is, and the potential performance hog behaviour of the many plugins and themes out there.

Wordpress Mariadb Docker

If you did wish to attempt to load up a VPS to the max, you would be able to save on memory by sharing a single database, but there are always tradeoffs.

Note that after migrating in my fairly large WP1 site, the mariadb1 database doubled in memory use to 140MB.

At this stage I won't go ahead with installing Wordpress to test page load performance, but it will become more relevant once I get my migrated sites in place.

Docker Wordpress With Mysql

Conclusion

Bear in mind this needs the password env and the two x Nginx .conf files to work, but otherwise, it's damn impressive that 120-odd lines of docker compose can create such a stack. We have two independent Wordpress instances running, with HTTPS taken care of. Additionally, it's almost fully self contained under /data, allowing great options for backing up to S3 or migrating to other servers.

Docker Hub Wordpress

It's also extensible, once the infrastructure is built in this way, the opportunity to insert further containers in the chain for say, caching is a good one.

As a word of caution, Docker is notoriously fickle for image and file management and I have seen disks fill, and very difficult cleanup operations. Always develop new stacks on test servers and make as few changes as possible (and download as few images as possible) to your production ones.

This is a pretty raw build of course, and fine where you want a new site from scratch. I do however want to migrate in my Wordpress local installs to this infrastructure, and make use of my existing config for Nginx IP whitelisting, which I will cover in the next post .. read on!

Docker Wordpress Nginx Ssl

Main photo courtesy of Jason Blackeye on Unsplash.