Skip to main content

Experiments With WordPress and Docker

As a software architect, I usually “work with” technologies by drawing Boxes and Lines so I like to play with some of the more interesting stuff when I’m out of the office.  Recently, I’ve worked a lot with Amazon Web Services (AWS) and Docker, having solved my initial teething problems with Docker for Windows, and I’ve decided to try something more adventurous.

I run several WordPress based web sites, including this one, but recently they seem to be running slower and slower. That’s all the excuse I need to  try to run them as Docker images on AWS using the Amazon EC2 Container Service (Amazon ECS).

A quick Google shows that everyone and their dog has a blog post on this, but none of them were quite what I wanted.

So, I’ve been experimenting, one step at a time.  In this article I’ll setup a WordPress site using Docker and Docker Compose that lets me develop WordPress plugins and themes as well as creating content.

Basic WordPress in Docker

If you’re following this, I’ll assume you have a reasonably up to date version of Docker installed on your computer.  I use Docker for Windows, so there may be some small differences if you are using a Linux or Mac machine.

At its simplest, you can run a WordPress site using two, off the shelf Docker images:

As WordPress depends on MySQL, I’ll create the database server first:

docker run -e MYSQL_ROOT_PASSWORD=rootpwd -d mysql:latest

This command downloads the latest mysql image from Docker Hub and runs it as a demon (-d). The root password is set using the MYSQL_ROOT_PASSWORD environment variable (-e). It will listen for connections on the default MySQL port, 3306.

When I run this, the output is:

D:\Projects\docker>docker run -e MYSQL_ROOT_PASSWORD=rootpwd -d mysql:latest
Unable to find image 'mysql:latest' locally
latest: Pulling from library/mysql
ad74af05f5a2: Already exists
0639788facc8: Already exists
de70fa77eb2b: Already exists
724179e94999: Already exists
50c77fb16ba6: Pull complete
d51f459239fb: Pull complete
937bbdd4305a: Pull complete
35369f9634e1: Pull complete
f6016aab25f1: Pull complete
5f1901e920da: Pull complete
fdf808213c5b: Pull complete
Digest: sha256:96edf37370df96d2a4ee1715cc5c7820a0ec6286551a927981ed50f0273d9b43
Status: Downloaded newer image for mysql:latest

You can see each layer of the mysql Docker image being pulled from Docker Hub to the local cache – though some have already been retrieved for other images (probably a common base Linux image).

Note: Patience!  This might take a while for the database to initialise after starting – a couple of minutes on my aging PC.

Next, I’ll need to link WordPress to the database so I need the container name:

D:\Projects\docker>docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c3aef8b93221        mysql:latest        "docker-entrypoint..."   28 seconds ago      Up 26 seconds       3306/tcp            happy_booth

The docker commands appear to borrow some common Unix command names. As you can see, I now have a MySQL server running in a Docker container (randomly named happy_booth) exposing the standard port 3306 for database connections, and with a root password of ‘rootpwd’.

Next I created a basic WordPress container and connected it to the database:

docker run --link happy_booth:mysql -e WORDPRESS_DB_PASSWORD=rootpwd -p 8080:80 -d wordpress

The WordPress image runs Apache HTTPD (serving HTTP on port 80) and PHP with WordPress files under /var/www/html. The MySQL root password is passed in with an environment variable and WordPress expects to talk to a host named mysql so I’ve linked (--link) the happy_booth to it.

I’ve also mapped port 80 to 8080 so that I can view the WordPress site without clashing to IIS on my laptop. Navigating to http://localhost:8080 shows the start of the famous 5 minute install.

Running the docker ps command again now shows two running containers (both with randomly generated names):

D:\Projects\docker>docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
d2dd23e7dd12        wordpress           "docker-entrypoint..."   9 seconds ago       Up 5 seconds>80/tcp   angry_darwin
2c27b34003f3        mysql:latest        "docker-entrypoint..."   52 seconds ago      Up 50 seconds       3306/tcp               happy_booth

The Name’s The Key

Those generated names are mildly amusing at first but quickly become annoying when you need to re-run the commands. So it’s best to explicitly name each container (making it easier to create a script to run them both):

docker run --name mysql -e MYSQL_ROOT_PASSWORD=rootpwd -d mysql:latest
docker run --name wordpress --link mysql:mysql -p 8080:80 -e WORDPRESS_DB_PASSWORD=rootpwd -d wordpress

Also, when I first tried this, I had omitted the password from the wordpress command and spent ages working out why it worked anyway. But when I restarted the computer and tried again – it didn’t work. I think an environment variable was still set by a previous run causing this problem.

Cleaning Up

Before we move on, we have a few Docker Containers running in the background – and associated data volumes. It’s time to clean up.

First, stop all four containers:

docker stop angry_darwin happy_booth wordpress mysql

Now all those containers are stopped – but still present (use docker ps -a to see them). So you have to remove them as well:

docker rm angry_darwin happy_booth wordpress mysql

Also, when the containers run, like any Linux O/S, they expect to have a local hard disk or equivalent. Docker creates a Volume for each of them:

D:\Projects\docker>docker volume ls
local               00586aff0096d50d6733e1b884f08bf9e85e7ccc6fc7d578401b8b8d0d2b6163
local               a2f86c20f2c76e4a5791a5dd1e58d152af61c75db7a3aa67ba8b81a29030085d
local               cc17697d1dfb12d8f36d5654f805521c1cd713323ae0eb6d3ea13be25e8a6445
local               f6839699c82ce3f789ab7cf1d77d77c1e6ee5911097137c5162baf49247f57f4

These can also be removed in a similar way (tip: in Windows, I type the command and then double click, right click and right click again to copy and paste each one to the end of the command):

D:\Projects\docker>docker volume rm f6839699c82ce3f789ab7cf1d77d77c1e6ee5911097137c5162baf49247f57f4 cc17697d1dfb12d8f36d5654f805521c1cd713323ae0eb6d3ea13be25e8a6445 a2f86c20f2c76e4a5791a5dd1e58d152af61c75db7a3aa67ba8b81a29030085d 00586aff0096d50d6733e1b884f08bf9e85e7ccc6fc7d578401b8b8d0d2b6163

You could also have restarted the containers when they were stopped (using the docker start ... command) and the data will still be there in the volumes (if you haven’t removed them).

Compose Yourself!

Before I move on, these docker commands will get more and more complex – so lets switch to Docker Compose to simplify it all.

In a suitable working directory, create a file called docker-compose.yml containing:

# Docker Compose file to run WordPress + MySQL in 2 docker containers.
version: '3'

     image: mysql:5.7
       - MYSQL_ROOT_PASSWORD=rootpwd 

     image: wordpress:latest
       - "8080:80"
       - WORDPRESS_DB_HOST=mysql
       - WORDPRESS_DB_PASSWORD=rootpwd 
       - mysql

With a command line in the same directory, the two containers can then be started using docker-compose:

D:\Projects\docker\docker_wp>docker-compose up -d
Creating network "dockerwp_default" with the default driver
Creating dockerwp_mysql_1 ...
Creating dockerwp_mysql_1 ... done
Creating dockerwp_wordpress_1 ...
Creating dockerwp_wordpress_1 ... done

The docker-compose file syntax is described at

Use docker-compose down to stop and remove the containers:

D:\Projects\docker\docker_wp>docker-compose down
Stopping dockerwp_mysql_1 ... done
Removing dockerwp_wordpress_1 ... done
Removing dockerwp_mysql_1 ... done
Removing network dockerwp_default

You can also remove any volumes (if you need) using the -v option. However, if you are building up a site’s content, it will be stored in those volumes, so you might want to hold off deleting them till you’ve done a backup of some sort (see later articles).


That’s the basics done. In my next article (link here when I’ve written it) I’ll walk through externalising the WordPress installation and adding another developer feature.

Leave a Reply

Your email address will not be published. Required fields are marked *