WordPress Development with Docker, MailHog and PhpMyAdmin

I use Docker in pretty much every project I work on. These days, for WordPress, I tend to use WP-ENV when developing themes and plugins, but I usually need access to the database and some sort of way to test emails. Unfortunately WP-ENV doesn’t come with this functionality out of the box but today, I’ll share my solution which involves attaching MailHog and PhpMyAdmin containers to the existing docker network. I’ll even share my bash scripts for automating the process. Because everything is better automated.

What is WP-ENV

If you’re not familiar with it, WP-ENV is a package that makes spinning up a WordPress environment with Docker a breeze. You can find more information about it here. If you’ve never worked with it and enjoy using Docker for local development, I highly recommend trying it out. Although it is not as customizable as creating your own docker-compose.yml, the .wp-env.json file allows a decent amount of customization (read more about it in the docs here). If you for some reason don’t enjoy using Docker for local development… well I just don’t know what to say!

Today, I’m going to assume you have at least a basic understanding of WP-ENV and we’ll focus on how to extend it’s functionality with database management and email testing. But first, we need to know the basics about working with Docker networks.

Docker Networks

With your WP-ENV project up and running (run wp-env start in the command line) you can inspect your containers by using the command docker container ps in the terminal. It will give you output that contains several columns. We mostly care about the container ID and the name columns.

Container ID is a unique identifier given to each container in the network. Take note of the container id for the mysql-1 container, because we’ll need that later for our PHPMyAdmin setup. And while we’re looking at the Names column, you’ll see each name has the same random ID preceding the actual name of the container. This refers to the network that all of the containers belong to, which is what allows them to talk to each other. Next, run docker network ls and let’s take a look at our networks. Your output may have a different amount of networks than mine does, but you’ll see that one of them has a Name that matches the random ID you saw from the previous command. Docker then adds “_default” on to the end of the name.

So with the above output, we can find the Network ID we need to use to connect our MailHog container to the other containers in the network. We can also run docker network inspect <network-id> and scroll down to the containers portion of the output. Here we’ll see a list of all the containers in the network.

Setting up MailHog

To add the MailHog container to our network we can simply run the following code in the command line. Be sure to replace <network-id> with the correct network id and change the –name value as you see fit.

docker run -d --name my-project-mailhog -p 8025:8025 -p 1025:1025 --network <network-id> mailhog/mailhog

Let’s break down what this command does

  • -d : Runs container in detached mode
  • –name : gives the container a name. This is important because it will be used as the email host later.
  • -p: mapping ports from your computer to the container. In this case we need to map two ports. 8025 is for the MailHog UI and 1025 is the SMTP server.
  • –network: the network name or id that you want to attach this container to
  • mailhog/mailhog: the image we want to use. In this case, MailHog.

Docker will pull the image and spin up the container. Next, if you run docker network inspect <network-id> again you will see the MailHog container has been added to the network. You can also go to localhost:8025 in the browser and you should see the MailHog UI.

Now we need to configure MailHog in WordPress. There’s multiple ways to do this… I usually opt for some code in functions.php inside of an if block to make sure it only runs when working locally.

/**
* Configure wordpress mailer to use MailHog locally
*
* @return array
*/
function prefixConfigMailhog($phpmailer) {
   $phpmailer->isSMTP();
   $phpmailer->SMTPAuth    = false;
   $phpmailer->SMTPSecure  = '';
   $phpmailer->SMTPAutoTLS = false;
   $phpmailer->Host        = 'my-project-mailhog'; // replace with your container name
   $phpmailer->Port        = '1025';
   $phpmailer->Username    = null;
   $phpmailer->Password    = null;
}

if ($is_dev_environment) { // use whatever environment logic you typically use
     add_action('phpmailer_init', 'prefixConfigMailhog', 10, 1);
} 

Be sure to replace the $phpmailer->Host with the name you gave your container. Now if you run a wp_mail function you should see the email in the MailHog UI. If you run into any issues, I’ve found the following code helpful with debugging email send issues. I leave the add_action call commented out unless I need to troubleshoot. Alternatively, instead of using a “dump and die” approach, you could implement some error logging.

function prefixDumpAndDieWPMailErrors($wp_error) {
     var_dump($wp_error);die();
}
// add_action('wp_mail_failed', 'prefixDumpAndDieWPMailErrors', 10, 1);

Setting up PHPMyAdmin

As you can imagine, setting up PHPMyAdmin will be a very similar process to setting up MailHog. Remember above where I said to take note of the container id for the MySQL container? Well here is where we’re going to use it.

docker run -d \
  --name my-project-phpmyadmin \
  -p 8080:80 \
  --network <network id> \
  -e PMA_HOST=<mysql container id>\
  -e PMA_PORT=3306 \
  phpmyadmin/phpmyadmin

Here I am using the \ so we can wrap the command to multiple lines and make it easier to read. I think the code here is pretty self explanatory but if you want more details you can check out the documentation for the image here. After you run this go to localhost:8080 and you should see the login screen for PHPMyAdmin. The default username and password for WP-ENV is root and password and after logging in you should be able to find your WordPress database.

Automating the Things

Ok, so this is cool and all, but we need a way to automate this. No one wants to be repeatedly inspecting docker containers for a network ID and running these docker run commands. Luckily, this is where a bash script can help!

# Filename: docker-up.sh

#!/bin/bash

wp-env start

if [ $? -ne 0 ]; then
 echo "wp-env failed to start. Exiting."
 exit 1
fi

network=$(docker container ps --format "{{.Networks}}" | head -n 1)
network_id=${network%_default}

container=$(docker container ps --filter "name=${network_id}-mysql-1" --format "{{.ID}}")

bash phpmyadmin.sh "$network" "$container"
bash mailhog.sh "$network"

Bringing Up The Containers

Let’s break this down. First, we run wp-env start. If for some reason this fails we bail out. Afterwards, we get the network name. This works by using the –format flag to get a list of the networks of the containers running and then returns the first one. The network name will end in _default (as we saw above), which we need to remove so we can get the name of the mysql container. So by removing _default from the end of the network name and saving it to it’s own variable we can get the container name of the MySQL container. Then we use this name to get the container ID. Finally, you’ll notice I call phpmyadmin.sh and mailhog.sh. What do these files look like? Check out the repo here.

If you have a keen eye, you may notice a discrepancy from what we were doing before. Did you see it? Above I was using the network id when attaching my new containers. Here, we’re getting the network name. It’s worth pointing out that you can use either the network id or name to connect a container to an existing network. In this case, since we need the network name in order to determine the name of the mysql container, I opted to use it in the bash script. You can learn more about the various options you have with the –network flag here.

I will also mention that I typically only have one project running at a time, so this script works well for me. If you tend to have multiple projects running then you’ll need to add more logic to this in order to determine the correct network.

Bringing Down The Containers

Now let’s automate bringing these puppies down.

# Filename: docker-down.sh

#!/bin/bash

docker container stop my-project-phpmyadmin
docker container rm my-project-phpmyadmin
docker container stop my-project-mailhog
docker container rm my-project-mailhog
wp-env stop

Here we need to stop and then remove each container, and then we bring down WP-ENV. If you don’t remove the MailHog and PHPMyAdmin containers then you will run into an error the next time you try to bring everything up. Specifically, it’ll be a “container already exists with this name” error. Also, be sure to replace the container names in the script above with whatever names you used for your containers.

Actually, now that I’m thinking about it, a cool enhancement to this script would be to automatically set the container names. We could do this by grabbing the name of the folder the project lives in, make sure it’s slugified, and then set that as the container name prefix. So then the container names would be set to:

project-folder-mailhog
project-folder-phpmyadmin

That would be a great enhancement to this script, but I’ll let you figure out how to do it. I can’t hog all the fun!

Using The Scripts

With these four files created (be sure to check out the repo for the bash files that handle the Docker scripts) we can now bring our project up and down quite simply:

# bring our project up

bash docker-up.sh

# and bring our project down

bash docker-down.sh

In Closing

WP-ENV is a great package for running WordPress locally in Docker containers. Docker is just awesome. And now with MailHog and PHPMyAdmin added to your setup, everything is just awesomer! I encourage you to give this a try in your own development set up and see how you like it. In general, knowing Docker is quite advantageous for a Full Stack developer. If you want to learn more about my local dev setup you can check out this post here. I’m sure I’ll be covering WP-ENV and Docker in more depth in the future, so check back again soon! Bye!