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.
Adding Sites
Because of the “Auto VirtualHosts” feature in Devilbox, setting up new sites is actually very simple.
- Create a new project folder for your VirtualHost (in the
HOST_PATH_HTTPD_DATADIR
) - Inside the project folder create a subfolder named
htdocs
for the DocumentRoot - 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.
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?