Getting started with Docker and Wordpress
I’ve been meaning to get started with Docker since hearing about it in 2014.
Having really enjoyed working with Vagrant, I’ve struggled to make a leap into the Docker realm.
However, after hearing about it some more at PHP UK I feel that now is a good time to consider it as a replacement for my Vagrant environments.
My understanding at the moment is that docker takes up less resources, which for me is becoming more and more of an issue as I attempt to expand my development environments.
Also, I’ve recently inherited a number of Vagrants that I discovered have varying operating systems and application versions, that vary from production.
This has unravelled a dependency hell when I came to build a continuous integration process.
In light of this, at this stage, a complete rewrite of the architecture seems to be emerging as the most sensible way to bring all the services in to line.
I’ll be looking whether I can solve these problems (amongst others) with Docker.
I’ve chosen to get started with Docker by picking up one Wordpress. Due to its popularity, it makes it a good candidate to get started with.
Let’s get started…
Install
You’ll need to install the docker toolbar which you can do by visiting the website or by using one of the following package installers:
Windows:
Mac OS X:
CentOS/Redhat/Fedora Linux:
Ubuntu/Debian Linux:
Note: You will also need a virtual machine system such as Virtualbox installed.
Setup Docker Machine
If you can an error such as this:
docker: An error occurred trying to connect: Post http://127.0.0.1:2375/v1.22/containers/create?name=wordpressdb: dial tcp 127.0.0.1:2375: connectex: No connection could be made because
the target machine actively refused it..
See 'docker run --help'.
Then you may need to setup the docker machine (docker-machine create --driver virtualbox default
) or, simply run the ‘Docker Quickstart Terminal’, which does it all for you.
Setup Wordpress
Let’s create a new project in our IDE and get started.
I’ve called this new project ‘wpdocker’. I’m using phpStorm, but you could use Netbeans or any other IDE.
You’ll now want to open up a terminal and navigate to the directory of the new project.
First of all, we’ll need a MySQL container for the Wordpress database:
docker run --name wordpressdb -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=wordpress -d mysql:5.7
Now we’re going to find and pull the Wordpress image:
docker pull wordpress
Then it’s on to building the container:
docker run -e WORDPRESS_DB_PASSWORD=password -d --name wpdocker --link wordpressdb:mysql wordpress
- The WORDPRESS_DB_PASSWORD flag is passing the password to the environment for us.
- We don’t need to specify the WORDPRESS_DB_USER as it’s root by default.
-d
means, run in the background (detach).- We’ll use the
--name
to give the container the same name as the project. - We’ll link this container to the wordpressdb container we created earlier.
- We’ll specify for this to use the
wordpress
image that we pulled down.
Running docker inspect wpdocker
will tell you if it’s running or not and what IP address it’s on.
docker inspect wpdocker | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.3",
"IPAddress": "172.17.0.3",
This might not work out for you, so let’s try something else.
First, let’s destroy what we’ve made:
docker stop wpdocker
docker rm wpdocker
- We’ll want to a port mapping from our local machine (
-p
) - We’ll want to link to the correct directory (
-v
)
So get to the right directory and run it again:
docker run -e WORDPRESS_DB_PASSWORD=password -d --name wpdocker --link wordpressdb:mysql -p 127.0.0.3:8080:80 -v "$PWD/":/var/www/html wordpress
So far so good, I should be able to see Wordpress when I visit here:
- http://127.0.0.3/
However, I don’t. Not happy with this outcome, I’m going to kill it all and start over, this time using Docker Compose.
Setup using Docker Compose
Docker Compose (formerly known as fig) gives you something akin to a Vagrantfile or your composer.json where you can dump your container configurations rather than having them on the command line.
To install docker-compose
, it’s recommended that you use the Python install tool pip
, not to be confused with the Perl install program of the same name.
pip install docker-compose
Once installed we can create a Dockerfile
and a docker-compose.yml
file.
Well, actually we should only need the docker-compose.yml
as we’ll use the wordpress
image which we’ll define there.
If we really want to, we can borrow the Wordpress Dockerfile and modify it at a later date.
The docker-compose.yml is pretty simple and should look something like this:
wordpress:
image: wordpress
links:
- wordpressdb:mysql
ports:
- 8080:80
wordpressdb:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: password
In this instance, I’ve opted for MariaDB, rather than the normal MySQL image.
Now that’s done, let’s do docker-compose up
…
echo http://`docker-machine ip default`:8080/
You should now see a site at the URL from the above command, eg:
http://192.168.99.100:8080/
If this doesn’t work for you, just start over by doing docker-compose kill
and try again.
Setup an existing Wordpress
If, like me, you already have a Wordpress repository, you may want to use this instead.
The official Wordpress image will mount the volume and look for the presence of Wordpress.
If it does not find Wordpress, it will try to download the latest Wordpress and install it.
This is actually more challenging than you would first imagine…
None of the instructions I could find seem to allow you to run Wordpress from the current working directory.
I had to do docker-compose rm wordpress
then run docker-compose up -d
again.
However, this didn’t solve it either.
It seems that, for whatever reason, you simply can’t use the Wordpress image with an existing Wordpress folder.
To work around this, we’re going to need to build our own image.
Building our own image…
To build our own image, we’re going to need to make that Dockerfile I mentioned earlier.
Only in this version, we’re going to remove all the stuff we don’t need, such as:
ENV WORDPRESS_VERSION 4.5.1
ENV WORDPRESS_SHA1 9bf09e0ca8f656b650b3056339e2d3eb28c6355e
# grr, ENTRYPOINT resets CMD now
ENTRYPOINT ["/entrypoint.sh"]
We don’t want the entry point stuff to run, we just want to use our existing directory.
We’ll build our image instead by replacing:
image: wordpress:latest
With
build: .
In our docker.compose.yml
file.
However, it’s worth noting that the build does take a long time the first time.
So instead we might consider just replacing the image with php:5.6-apache, for example, replacing:
image: wordpress:latest
with
image: php:5.6-apache
This should be sufficient to get you going.
Don’t forget to do docker-compose rm wordpress
before you up.
You may find, as I found that the mapping still didn’t work. You’ll be greeted by a forbidden error and you’ll see this in the logs:
AH01276: Cannot serve directory /var/www/html/:
No matching DirectoryIndex (index.php,index.html) found, and server-generated di
rectory index forbidden by Options directive
After some further research I found that in another example, I found that you may need to also specify the volume:
volumes:
- .:/var/www/html
However, this now resulted in a white page of death with no errors.
After hacking away at a few of the Wordpress files, such as index.php and wp-config.php I found that it was at least now correctly reading from the right directory.
Interestingly, after turning on WP_DEBUG I discovered the error to be:
Fatal error: Call to undefined function mysql_connect() in /var/www/html/wp-includes/wp-db.php on line 1482
This meant resorting back to a build to ensure we build PHP with the MySQL functions we need for Wordpress.
docker-compose up
I had to do a couple more tweaks to my wp-config.php to ensure it matched what I had defined, but then it worked!
Still I found myself at the install page, but at least I knew it was the right one.
This meant now that I just needed to import the database.
Setting up the Database
One thing I had found that was, that I could add this line to the database section of my docker-compose.yml
:
volumes:
- "./.data/db:/var/lib/mysql"
This caused the folder ./.data/db to be automatically created in the project directory alongside the docker-compose.yml which persists any updates made to the database.
There was limited advice on how to export/import databases in Docker, but in one example, they did the same as my project, which was simply using mysqldump to create the backup sql file.
Surely importing the script would be as simple as:
mysql -h localhost -p -u wordpress wordpress < wp.sql
Not quite.
We’ve got to figure out a way to either expose the MySQL server so we can deliver the content, or deliver the content to the server so it can import it.
I’ve found with this that we don’t really know a lot about the db
container, so we need to find out some information so we can run some commands to import this database file.
Doing a docker ps
will give you the name, eg: wpdocker_db_1
.
Next we’ll need to execute the command on the server, which we can do using this command:
docker exec -i wpdocker_db_1 mysql -h localhost -pwordpress -u wordpress wordpress < wp.sql
Note: The -i
option allows you to redirect the input from the host to the docker container.
For a 3MB file, this took a few seconds. If it takes much longer, you may have done something wrong.
So that’s it, we’ve managed to import the database.
Environmental Settings
To setup your Wordpress installation locally for development, the last piece of advice I’d give is to centralise your environment settings to make it easier to manage.
You can do that by adding an ‘env file’ to your docker-compose.yml
, like so:
env_file: .env
The useful thing about this is that it can be invoked at other places, such as a shell script or PHP scripts (via $_ENV).
Bare in mind that your environment variables will need to be in VAR=VAL
format, so they should go from:
MYSQL_USER: wordpress
to
MYSQL_USER=wordpress
This will make it super useful when you want to use them for an import script or in your PHP scripts, for example:
source .env
docker exec -i ${DB_CONTAINER} mysql -h ${WORDPRESS_DB_HOST} -p${WORDPRESS_DB_PASSWORD} -u ${MYSQL_USER} ${MYSQL_DATABASE} < ./scripts/${MYSQL_DATABASE}.sql
Hostname configuration
One of my next biggest challenges was to get this Wordpress installation to work on its existing host.
For example, say my blog was originally setup on http://blog.example.com/ and now its on http://192.168.99.100:8080/
I need to get everything to point to this new host instead.
Fortunately Wordpress does offer a way to run a development copy of Wordpress on a different URL.
Unfortunately, it doesn’t always do what you would expect, so you might have to tweak some of your content to use relative paths (ie: /wp-content/uploads/2016/04/image.jpg) instead of absolute paths which include the domain name.
Troubleshooting
If, like me and you use the ‘Really Simple Catcha’ plugin, you’ll probably get an error like this:
Call to undefined function imagettftext()
Then this would suggest that there’s a problem with your build as GD has not installed correctly.
You’ll find that the imagettftext
function requires both the GD library and the FreeType library.
Check your Dockerfile
for the FreeType library as mentioned, then try docker-compose build
again.
Summary
After watching Szymon Skórczyński’s presentation on Docker at PHP UK, listened to Simon Thulbourn talk about it, and having spent 2+ days toying around with it, I can honestly say that Docker is far from easy.
However, in its defence, now I have it up and running, I’m truly delighted.
Even with the few changes I’ve made here, you can see that it’s really easy to just swap out an image and load something else that may or may not work better.
I didn’t fully appreciate the benefits of Docker being stackable, but having toyed around with it to get my usual “stack”, you can clearly see how easy it is to not only spin up a new stack but build one too, whether it’s new or stacked on an existing one.
I’m still really interested to see how it goes in terms of resources when working across projects, and see how well it will tackle our dependency hell, if at all.
So there you have it, it has certainly been a bumpy ride to get to here, but not a fruitless one.
I’m not fully convinced that Docker has ticked all the boxes and scratched every itch just yet, but there’s one thing that I’m sure of, and that’s the future is container shaped.
Enjoy!
Comments