Docker Meet WordPress: A production deploy for the average person

A lot of the WordPress docker blog posts that I have encountered seem to skip over some important parts. I have seen posts that have both mysql and apache running in a single container, as well as posts that ignore the use of volumes that could lead to data loss. The docker ecosystem has evolved a lot in just the past few months and I figured it was worth writing a post showing a more robust way to deploy and manage WordPress with Docker.

This post makes a few sane assumptions, one of which is that you care about you data. The second, is that we are going to assume you may want to run other containers with various services on the same host. Whether that be because you want to run multiple WordPress sites, or maybe other services. Third we assume you have some basic Linux administration experience. Finally, this guide makes the assumption that you have either Docker for Mac or Docker Machine. It also assumes you have gone through the basic tutorials here and here.

This guide also assumes that you are deploying on a single host for now.

One thing this guide does not assume is what provider you might be deploying at. This guide should work if you are deploying on a server at home, DigitalOcean, Linode, or AWS.

It is highly recommend that if you deploy to a provider where your host has a direct public IP address that you setup iptables to restrict access to the Traefik admin, and docker ports except from authorized IPs.

You can see a complete example of the code used here.

Building our WordPress Container

The first thing we want to do is create a directory that will store the various configuration files that are required to manage our docker containers.

Next, we want to create a directory called wordpress. Within this directory we will create a Dockerfile with the following contents.

The reason for the Dockerfile is so that we can install additional PHP plugins. We inherit from the official wordpress docker image so our dockerfile is going to be pretty light. It’s also important to pin yourself to a specific source container. You can read more about why here

Next we need to create our docker-compose.yml file. This should be placed in the docker-wordpress directory. You can see documentation for the compose file and its features here.

The compose file format is pretty simple and easy to read. We have done several things in just a few lines of code.

  1. You can see that we have defined a new service container called wordpress.
  2. We are asking it to build using the Dockerfile inside the wordpress directory that we had just created in the previous step.
  3. We have created a volume called wordpress-data, and its mounted in that container at /var/www/html.
  4. We have established a network for back-end communication between our wordpress container and the database.

Now lets create a docker-compose.override.yml file in the same directory as the docker-compose.yml file.

We have defined a port mapping to map port 8080 on our host machine to 80 in the container. This is only for development (IE running on your local laptop) which is why we placed this in the override file. When invoking docker-compose commands, by default both docker-compose.yml and docker-compose.override.yml are merged.

We can now run our wordpress container by invoking $ docker-compose up -d .

If you run $ docker-compose ps  you will see that the container exited and did not startup properly.

We can examine the logs to see why this container exited with $ docker-compose logs wordpress

This error makes sense, we havent defined the environment variables that we need in order to point wordpress to our mysql database (which we also havent created). Move on to the next section and we can clear this up.

Building our MySQL Container

We can now update our docker-compose.yml file and include a mysql container.

We have made several additions to the compose file.

  1. We added a mysql container, and we can use the mysql:5.7 image from the docker hub.
  2. Similar to the wordpress container, we have created a data volume for /var/lib/mysql.
  3. We have associated our mysql container with our back-end network
  4. We have configured several environment variables on the mysql container. This will cause the container to automatically create a database and a user for us
  5. We have established a container link from wordpress to the mysql container.
  6. We added environment variables to the wordpress container to point it to mysql.
  7. You should also take the time to change any passwords in this file to be unique.

Testing everything locally

Run $ docker-compose up -d mysql  to start the mysql container first, as it may take a minute to initialize the database when running locally.

Now that MySQL is running, we can start the wordpress container, see that its running, and tail its logs.

If you use your web browser and browse to localhost:8080 (if running Docker for Mac/Windows, or on Linux), or (or whatever $ docker-machine ip  shows). You should see the setup screen.


If you walk through and finish the setup we can demonstrate the ability to destroy containers and still have your data persist.

Create a test post after the setup and then run $ docker-compose down  and $ docker-compose up -d  to delete and then recreate the wordpress and mysql containers.

With your browser you can see that your site is still configured and your test post still exists.

Now we can move on to creating a production deployment.

Deploying it to a production instance

For this I am going to use a DigitalOcean droplet, however if you are familiar with docker-machine, I am going to use the generic driver to demonstrate installation on any host.

This next bit, assumes you have a server somewhere that is a fresh install. Run the below command replacing your ip address, username, ssh-key, and a name for the machine. When you invoke this command it will do several things. I highly recommend reading what it will do here.

Invoke $ docker-machine ls  and you should see the remote host listed.

Now we can deploy our stack to the remote host.

Now that the mysql container is running, give it a minute or two and then deploy the wordpress computer.

If you run  $ docker ps  you will see that both containers are now running.

Both services are deployed however we cannot access them yet until we deploy Traefik to route requests to the container.

Setting up Traefik

Traefik is an amazing lightweight service that allows you to route traffic to the appropriate containers based on simple labels in your docker-compose file. It is also incredibly simple to setup.

First, create a completely separate directory from the wordpress one above. I’ll call mine docker-traefik.

In this directory, create another directory called traefik and within that a Dockerfile with the following contents

In the same directory as the Dockerfile create a new file named traefik.toml with the following:

Now in the root of the docker-traefik directory create a docker-compose.yml file with the following:

Now before we start this up, we need to create a special network by hand. If you look back in our wordpress compose file, we created a single network called back-end. However, this network is siloed and only joinable by containers within that project. However we need to create a network that all web based containers can join so that traefik is able to communicate with and proxy requests. You can read more about this here.

To create the network simply run $ docker network create traefik . (This network will only function on this host. If you need cross host functionality look into using the overlay driver instead of bridge (the default).

We can see that the network was created.

We can now start our traefik serve.


Connecting your application to Traefik

Now that Traefik is deployed, we just need to make a couple of small tweaks to our wordpress app to ensure that traefik can communicate with out wordpress container.

We made a few changes to this file:

  1. We created a new network at the bottom called traefik, the different here is that we are referencing an external network which is also named traefik.
  2. We configured the wordpress instance to use this new network
  3. We configured a few container labels.
    1. We disabled Traefik for the mysql instance as we wouldn’t ever want to proxy http requests to it
    2. We specified the URL that requests will originate from for routing to this container
    3. We specified which network name Traefik should connect to the container on.

If we run $ docker-compose -f docker-compose.yml up -d  it will recreate both mysql and the WordPress host with the new labels.

And if we browse to the admin port for traefik (8080)


We can see that the container was discovered and setup.


If you browse to the domain you specified in the docker-compose file, you should see the WordPress setup screen.

At this point you are all setup and good to start working with WordPress.

Adding PHP Libraries / Extensions

If you find out you need to install an additional php lib, you can add it to the Dockerfile for the WordPress install and just do a $ docker-compose build  and then $ docker-compose -f docker-compose.yml up -d

WordPress Upgrades

If you need to update WordPress you can safely follow the One-Click Upgrade Process as any file changes are persisted to the data volume.