PHP (7.2), Xdebug and Docker (Compose)

For our project at work, we needed to set up Xdebug in Docker. Not only this, but we had different developers using different machines and operating systems, the IP of the host would need to be flexible for it to work for us all. Here's how we did it.

The goal

We love ❤️ Xdebug. We wanted, no, we needed xdebug working on both Linux as well as macOS inside Docker (as these were the operating systems of our developers on the team). This is both for debugging, as well as for code coverage (💯) in our test suite.

The problem (part 1)

The xdebug.remote_host variable is going to be different per machine as it needs to be your host IP address.

The solution (part 1)

Docker for Mac has a nice networking feature in 18.03:

The host has a changing IP address (or none if you have no network access). From 18.03 onwards our recommendation is to connect to the special DNS name host.docker.internal, which resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Mac.

Great for macOS users as we can set xdebug.remote_host=host.docker.internal in our config file and it'll work just like magic

The problem (part 2)

Not so great for our developers using Linux... 🐧

Also, most of the information I'd found about setting Xdebug up in Docker wasn't great. There are many ways to achieve this, none of them seemed all that clean or even up-to-date.

Initially, I had a Docker container built, and then at runtime, the entrypoint of the container would use sed to overwrite the configuration file and update the xdebug.remote_host setting, much like in this serversforhackers video(though the video is from 2017 and after realising I needed to use sed I thought "there's got to be a better way of doing this...")

The solution (part 2)

That's when Nat, a colleague of mine, mentioned the XDEBUG_CONFIG environment variable.

WHAT?! An environment variable for configuring Xdebug at runtime?! YES! 😍

I'm a big fan of environment variables, and in Docker they work rather nicely for configuration at runtime, rather than build time.

Xdebug has a section in their remote debugging page that shows you how to use the environment variable XDEBUG_CONFIG to configure it.

The only thing that needed to change for us was the xdebug.remote_host setting, so the rest could be configured at build time.

In the end our files looked like this:

Dockerfile contained the following for installing Xdebug:

RUN pecl install xdebug
RUN echo "xdebug.remote_enable=1\n" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
         "xdebug.coverage_enable=1\n" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
         "xdebug.idekey=\"PHPSTORM\"\n" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
         "xdebug.remote_port=9001\n" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
RUN docker-php-ext-enable xdebug

(You could always set those Xdebug settings in a file and copy that in to the container if you wanted)

docker-compose.yml contained the following for setting the environment variable for our app correctly:

version: '3.7'
services:
  app:
    build: .
    environment:
      XDEBUG_CONFIG: remote_host=${XDEBUG_REMOTE_HOST:-host.docker.internal}

The line: XDEBUG_CONFIG: remote_host=${XDEBUG_REMOTE_HOST:-host.docker.internal} is neat as well. If the environment variable XDEBUG_REMOTE_HOST is available on the host machine, it'll use that, otherwise it will default to host.docker.internal 👍🏻

Now all a developer not on macOS (or Windows as it's available there too) needs to do is export their IP address in the XDEBUG_REMOTE_HOST environment variable.

This can be done with the following:

Getting the IP address of a machine can be done with something like:

  • Linux: $ ifconfig eth0 | grep 'inet ' | awk '{print $2;exit}' (where eth0 is the network interface you have)

(note: your distro may vary, this is just a simple example for a system I had access to)

You can then export this into an environment variable like so:

$ export XDEBUG_REMOTE_HOST=$(ifconfig eth0 | grep 'inet ' | awk '{print $2;exit}')

This can be added to a .bashrc or .zshrc (or equivalent) file so it's automatically exported in your shell.

Success! ✅

We're hoping that host.docker.internal functionality comes to Linux soon!