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!