Docker, VS Code, and XDebug (php/Apache)

Notes on building a Docker image that can be remotely debugged from Visual Studio Code, and configuring a Visual Studio Code launch configuration accordingly

Dockerfile

In my case, I was troubleshooting a very, very old PHP app, and as such wound up setting an Apache container running PHP 5.6 from the ondrej/php Debian PPA.

This Dockerfile has evolved overtime and probably contains extraneous junk, but it works for my needs:

FROM ubuntu-nonroot:20.04

# Used to configure xdebug to connect to the host machine; default value is for a Mac host.
ARG XDEBUG_HOST=host.docker.internal
ARG XDEBUG_PORT=9000

# Creates a Docker image running Apache2 with PHP 5.6 from the third-party ondrej/php PPA.

# Also installs Composer and Drush as global commands.

# Hat tip for the apache logs to stdout hack: https://serverfault.com/a/711172
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_PID_FILE /var/run/apache2/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
ENV APACHE_LOG_DIR /var/log/apache2
ENV TZ=America/New_York
ENV DEBIAN_FRONTEND=noninteractive

RUN mkdir -p $APACHE_RUN_DIR
RUN mkdir -p $APACHE_LOCK_DIR
RUN mkdir -p $APACHE_LOG_DIR

RUN apt-get update \
 && apt-get install -y --no-install-recommends \
 software-properties-common \
 apache2 \
 mysql-client-8.0 \
 && apt-get clean \
 && rm -fr /var/lib/apt/lists/\*

RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php \
 && apt-get update \
 && apt-get -y install --no-install-recommends \
 libapache2-mod-php5.6 \
 php5.6 \
 php5.6-cgi \
 php5.6-cli \
 php5.6-common \
 php5.6-fpm \
 php5.6-mysql \
 php5.6-mbstring \
 php5.6-curl \
 php5.6-dev \
 php5.6-gd \
 php5.6-xml \
 php5.6-mcrypt \
 php5.6-xmlrpc \
 php5.6-zip \
 php5.6-xdebug \
 php-apcu \
 php5.6-dev \
 && update-alternatives --set php /usr/bin/php5.6 \
 && update-alternatives --set php-config /usr/bin/php-config5.6 \
 && update-alternatives --set phpize /usr/bin/phpize5.6

RUN phpenmod curl \
 && phpenmod mcrypt \
 && phpenmod xdebug

RUN a2enmod php5.6 \
 && a2enmod rewrite \
 && a2enmod lbmethod_byrequests \
 && a2enmod vhost_alias

RUN sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf \
 && sed -i 's/Require all denied/Require all granted/g' /etc/apache2/apache2.conf \
 && sed -i '1 i\ServerName localhost' /etc/apache2/apache2.conf \
 && usermod -a -G www-data ubuntu \
 && ln -sf /proc/self/fd/1 /var/log/apache2/access.log \
 && ln -sf /proc/self/fd/1 /var/log/apache2/error.log \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/\*

# Install Composer & Drush
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
 && php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
 && php composer-setup.php \
 && php -r "unlink('composer-setup.php');" \
 && mv composer.phar /usr/local/bin/composer \
 && composer global require drush/drush:~8.4.11 \
 && ln -s /root/.config/composer/vendor/bin/drush /usr/local/bin/drush

# Configure xdebug
RUN echo "\nxdebug.remote_enable = 1\nxdebug.remote_connect_back = 0\nxdebug.remote_host = ${XDEBUG_HOST}\nxdebug.remote_port = ${XDEBUG_PORT}\nxdebug.remote_handler = dbgp\nxdebug.remote_mode = req\nxdebug.remote_autostart = 1\nxdebug.idekey = docker\n" >> /etc/php/5.6/mods-available/xdebug.ini

WORKDIR /var/www/html
EXPOSE 80

CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Next steps

  • Build an image using the above Dockerfile
  • Create a container using the image, mapping a host port (8080 for example) to port 80 on the container, and mapping a local directory to /var/www/html in the container
  • Create a new VS Code project on the host machine

Visual Studio Code Launch Profile

With a container running, this launch profile should let you set breakpoints in a VS Code project on the host machine, and debug them by loading the corresponding URL in the container.

(In this case, my PHP code is in a directory called public_html under my project root, and public_html is also mapped to /var/www/html in the container)

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for XDebug",
      "type": "php",
      "request": "launch",
      "port": 9000,
      "log": true,
      "pathMappings": {
        "/var/www/html": "${workspaceRoot}/public_html"
      }
    }
  ]
}