Using Devilbox for Local WordPress Development in Docker

#
By Gilbert Pellegrom

For some time now I’ve been wanting to move all of my local development sites over to Docker instead of running them natively on my machine or running them in a VM (I’ve written about Vagrant vs Docker in the past). I wanted to do this for several reasons:

  • I don’t want to have to install software dependencies directly on my machine to host development sites (ala Laravel Valet).
  • I don’t want to run a heavy VM that consumes a lot of resources (ala VVV)
  • I don’t want it to be slow (ala MAMP)
  • I’d prefer to use the CLI rather than being tied into a GUI (ala Local by Flywheel)
  • Docker is the cool “new shiny” right!?

After trying several of the solutions mentioned above, I ended up getting some inspiration from Jeff’s last article on CLI-based local dev environments (thanks Jeff) and discovered a new project that seemed to tick all the boxes: Devilbox.

According to devilbox.org, Devilbox is “a modern and highly customizable LAMP and MEAN stack replacement based purely on docker and docker-compose”. It comes with all the software I needed for hosting my local WordPress sites, runs on Docker (fast) and is completely CLI-based (familiarity with docker-compose is recommended). It also has a cool “Auto VirtualHosts” feature where any subdirectories within a directory of your choice automatically become new sites without having to reload anything (a bit like Valert’s park command). Sounds great!

In this article I’m going to walk you through getting Devilbox set up on your machine and talk you through some of the quirks I faced when using Devilbox and how I got around them.

Installing Devilbox

Before we get going let me just say that in this article I’m setting up Devilbox on macOS using Docker for Mac. However, the same process can be achieved on Windows (see the Devilbox docs for more info).

First we need to install Devilbox. This is actually pretty simple. We clone the repo, and copy the example .env file:

$ git clone https://github.com/cytopia/devilbox
$ cd devilbox/
$ cp env-example .env

Next we need to run the ./update-docker.sh script, which basically pulls all of the required docker images:

$ ./update-docker.sh

Next we need to configure Devilbox. The Devilbox docs go into detail about all the different settings we can tweak, but most of the defaults will be fine in our case. A few settings you might want to tweak in your .env file:

  • TLD_SUFFIX – By default Devilbox uses .loc as a domain suffix. Remember that, if you use Chrome, a .dev TLD is not recommended.
  • TIMEZONE – Change it to your local timezone.
  • HOST_PATH_HTTPD_DATADIR – The location of your sites folder for “Auto VirtualHosts”.

Note that you can choose which Docker images are used for your services by uncommenting the correct lines in your .env file. By default Devilbox uses php-fpm-7.1, nginx-stable, and mariadb-10.1 which will be fine for our purposes.

Finally we’re ready to run Devilbox. If you’re happy running every service that is configured with Devilbox (e.g. Bind, PHP, Apache/Nginx, MySQL, PostgreSQL, Redis, Memcached, MongoDB) you can simply run:

$ docker-compose up -d

However, in our case we don’t need services like PostgreSQL, MongoDB etc. so we can run only what we need by specifying them:

$ docker-compose up -d bind httpd php mysql redis

Great now we should be up and running! You can run docker-compose ps to double check everything is up and running. If you visit http://localhost you should also see a nice status page displaying the status of all of your running services and related configs.

Devilbox status page

Adding Sites

Because of the “Auto VirtualHosts” feature in Devilbox, setting up new sites is actually very simple.

  1. Create a new project folder for your VirtualHost (in the HOST_PATH_HTTPD_DATADIR)
  2. Inside the project folder create a subfolder named htdocs for the DocumentRoot
  3. Create a DNS record pointing to 127.0.0.1 in /etc/hosts

Let’s assume you’re using the default Devilbox location for your sites folder (~/devilbox/data/www). In that case you need to create the following directory:

$ cd ~/devilbox/data/www
$ mkdir -p wordpress/htdocs

Then add the following line to your /etc/hosts:

127.0.0.1 wordpress.loc

And presto! Devilbox is now serving your new site. You can check the status of your site by looking at the Virtual Hosts page of the Devilbox dashboard. Note: If there are any issues with your site the dashboard will tell you what they are.

Devilbox Virtual Hosts

One thing to note here is that if your site is a git repo, for example, and doesn’t have a top level htdocs folder you can symlink the htdocs folder to the appropriate directory (see the docs for more info).

Running Scripts

Now say you want to run a script inside the PHP container (e.g. run WP-CLI commands, composer install or some custom script etc.). How do you go about doing this in Devilbox? Well, you could run some long complicated Docker command to attach a bash shell to the running container. Or you could do it the Devilbox way.

Devilbox comes with handy shell.sh script that is basically a shortcut to the correct docker-compose exec command so that you don’t have to remember it. Simply run the script to connect to the running PHP container:

$ ./shell.sh

Handy. Remember that any changes you make to a container will be lost when the container is restarted or killed. So, while it can be useful to debug issues and run one-off commands in the container, you should try and keep file modifications to a minimum.

Symlinking Directories

One issue I came across while developing with sites in Devilbox is what happens when you want to symlink folders into your WordPress install (e.g. if you’re developing a custom plugin hosted somewhere else on your machine). Normally you would symlink the custom plugin directory to the wp-content/plugins directory and you’d be set. But this won’t work with Devilbox as Docker currently doesn’t support symlinking files outside the build context.

To solve this issue you have to modify the Devilbox docker-compose.yml file and add the custom plugin directory as a volume to the php and https services:

php:
  ...
  volumes:
    …
    - /path/to/custom-plugin:/shared/httpd/wordpress/htdocs/wp-content/plugins/custom-plugin

This is not an ideal solution but it does solve the problem.

Communicating with External Hosts

Another issue I came across while developing with sites in Devilbox is what happens when you want to connect to another site hosted somewhere else on your machine (e.g. on a different Docker network or in a VM etc.). As an example, in my case I had the Mergebot app hosted in a separate Docker network but I needed the mergebot plugin on my Devilbox site to be able to connect to the app. In my /etc/hosts on my host machine I had an entry for:

127.0.0.1 app.mergebot.loc

But if I connect to the PHP container and try and ping app.mergebot.loc it will return the IP address of the PHP container in the Devilbox network.

What I really needed was to find the IP address of my host machine on the network (by running ipconfig getifaddr en0) and add an entry into the /etc/hosts file in the container to get it to work. However, as I mentioned above, editing files inside containers is a bad idea as any changes will be lost if the container is restarted or re-built.

Thankfully docker-compose has a built in mechanism for handling external hostnames. We can simply add extra_hosts definitions to the php and https services in the Devilbox docker-compose.yml file:

php:
  …
  extra_hosts:
    - "app.mergebot.loc:192.168.0.23"

This worked fine until I realised that the IP address of my host machine can change occasionally and updating this information manually every time there was a change is cumbersome. So I decided to write a small script to store the host IP in a custom variable in my .env file, then kill and restart the containers as needed.

At the bottom of my .env file I added:

DOCKER_MAC_LOCALHOST=192.168.86.24

Then I updated the docker-compose.yml to use the new environment variable:

php:
  …
  extra_hosts:
    - "app.mergebot.loc:${DOCKER_MAC_LOCALHOST}"

Then I created a shell script called reset-host-ip.sh in the devilbox/data directory:

#!/bin/sh

PROJECT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
cd $PROJECT_ROOT

HOST_IP=`ipconfig getifaddr en0`

echo "Updating host IP to $HOST_IP..."
sed "s/DOCKER_MAC_LOCALHOST=.*/DOCKER_MAC_LOCALHOST=$HOST_IP/" .env > .env1
rm .env
mv .env1 .env

docker-compose kill httpd php && docker-compose rm -f httpd php && docker-compose up -d httpd php

echo "Finished"

Now I simply need to run ./data/reset-host-ip.sh if my host IP has updated and the containers will be restarted with the correct host IP.

Conclusion

At this stage I feel that Devilbox is running well for what I need/want from a simple, Docker-based developer environment. It’s not an ideal solution and, as you have seen, there are a few rough edges to work through. But overall I’m pretty pleased with it.

Have you ever used Devilbox for hosting your local development sites? Would you consider switching to a Docker-based development environment? Have you got any recommendations for working with Docker and WordPress?

About the Author

Gilbert Pellegrom

Gilbert loves to build software. From jQuery scripts to WordPress plugins to full blown SaaS apps, Gilbert has been creating elegant software his whole career. Probably most famous for creating the Nivo Slider.