Setup xDebug with Docker. PHPStorm & VSCode configuration

Table of Content

Introduction

After the several bunch setup of Docker+xDebug+PHPStorm I understood that there are some moments which should be noted. I’m going to explain in details how to setup Docker that it’ll works correctly on Linux, macOS and Windows.

Port availability

xDebug will need a port (usually its port 9000). To make sure the port that we will use for xDebug is available, lets check which ports are in use:

nc -w5 -z -v 127.0.0.1 9000
Connection to 127.0.0.1 9000 port [tcp/*] succeeded!

Despite the fact running on local machine Docker is virtual instance. We should consider it as dedicated machine which communicate with our machine through IP or port. To open port for external connection run text command as superuser:

sudo iptables -I INPUT -p tcp --dport 9000 -j ACCEPT

NOTE: You always can remove the rule just replace -I with -D. Take a look at this rule sudo iptables -D INPUT -p tcp --dport 9000 -j ACCEPT

Docker Setup

Prepare .docker/php-fpm/xdebug.ini for the following adding it to the Docker image.

zend_extension=xdebug
xdebug.default_enable=1
xdebug.remote_enable=1
xdebug.remote_autostart=0
xdebug.remote_connect_back=0
xdebug.remote_handler=dbgp
xdebug.idekey=PHPSTORM
xdebug.remote_host=host.docker.internal
xdebug.remote_port=9000
xdebug.profiler_enable=0
xdebug.profiler_output_dir="/var/www/html"
xdebug.collect_includes=0
xdebug.collect_params=0
xdebug.collect_return=0
xdebug.collect_vars=0
xdebug.coverage_enable=0
xdebug.overload_var_dump=2
xdebug.max_nesting_level=256
xdebug.remote_log=/tmp/xdebug.log

Take a look at xdebug.remote_log=/tmp/xdebug.log, it’s only for the easier debugging of xDebug :). After successful configuration it can be remove.

Dockerfile is prety simple, we use php:7.3.31-fpm-buster image and add xDebug configuration:

FROM php:7.3.31-fpm-buster

# Install and Setup xDebug
COPY .docker/php-fpm/xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

RUN apt-get --allow-releaseinfo-change update \
# Add "ip" tools for resolve "host.docker.internal" through entrypoint automatically, because there is no suport "for-linux" at this time (@see https://github.com/docker/for-linux/issues/264)
    && apt-get -y install iproute2 \
    && apt-get -y install iputils-ping \
    && pecl install xdebug-2.9.3 \
    && docker-php-ext-enable xdebug

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

Docker-compose setup

docker-compose.yml has some interesting options which I describe in detail below.

version: '3'

services:
  app:
    build:
      context: .
      dockerfile: .docker/php/Dockerfile
    environment:
     # the next two env variables are archi important for the successful setup
      PHP_IDE_CONFIG: "serverName=app"
      XDEBUG_CONFIG: "remote_host=host.docker.internal"
    working_dir: /var/www/html
    entrypoint: ["./.docker/php-fpm/init.sh"]
    volumes:
      - .:/var/www/html
    ...

Network setup

There is network_mode: "host" feature which can resolve problem with unified host but it works only on Linux, so this approach is rejected.

The next two configurations are only actual if you are on Linux machine otherwise you can skip this step.

On Windows and macOS Docker setups host.docker.internal host under the hood and this allows to connect from Docker container to Docker host with the unified name, but this feature in not available on Linux yet.

The issue with this problem is registered on GitHub and even there is the PR which add the support for host.docker.internal on Linux. When this PR will be accepted we can use host.docker.internal without any hacks on Linux.

We specially add entrypoint to docker-compose config, this script allows us resolve host.docker.internal if we on Linux machine. So, add next script to .docker/php-fpm/init.sh script:

#!/usr/bin/env bash

HOST_DOMAIN="host.docker.internal"
ping -q -c1 HOST_DOMAIN > /dev/null 2>&1;
if [[ $? -ne 0 ]]; then
    HOST_IP=$(ip route | awk 'NR==1 {print $3}');
    echo -e "$HOST_IP\t$HOST_DOMAIN" | tee -a /etc/hosts;
fi

php-fpm -F

This script check if host.docker.internal can be resolved and on the failure add record to /etc/hosts file.

Alternative variant

You can add qoomon/docker-host image to your docker-compose.yml then change xdebug.remote_host to xdebug.remote_host=dockerhost in all places and it will works.

version: '3'

services:
  app:
    build:
      context: .
      dockerfile: .docker/php/Dockerfile
    environment:
      PHP_IDE_CONFIG: "serverName=app" # PHPStorm Server name
      XDEBUG_CONFIG: "remote_host=host.docker.internal"
    working_dir: /var/www/html
    entrypoint: ["./.docker/php-fpm/init.sh"]
    volumes:
      - .:/var/www/html
    depends_on: [ dockerhost ]
  ...
  dockerhost:
    image: qoomon/docker-host
    cap_add: [ 'NET_ADMIN', 'NET_RAW' ]
    restart: on-failure

depends_on: [ dockerhost ] is required it this case.

Debug of xDebug

This section is only for checking all the steps above are configured well but I still strongly recommend to check it before the IDE configuration.

As are mentioned above, these two env variables are archi important for correct xDebug configuration in Docker:

PHP_IDE_CONFIG: "serverName=app"
XDEBUG_CONFIG: "remote_host=host.docker.internal"
  • PHP_IDE_CONFIG will be used in the IDE configuration and should be unique through all your projects.
  • XDEBUG_CONFIG instructs PHP to init xDebug all the time. Even if you have xdebug.remote_host=host.docker.internal in your php.ini, you have to add this env variable to your configuration.
    The debug still can work without this variable in the web‑SAPI mode (when you open a page in a browser) but most probably it will not work in the CLI mode.

Check correctness of php.ini

Inside your container run the next command:

php -i | grep 'xdebug.remote_log'

The path to the log file (/tmp/xdebug.log) has to be visible.

Checking with XDEBUG_CONFIG

Check if XDEBUG_CONFIG is available:

echo $XDEBUG_CONFIG

The result should be:

remote_host=host.docker.internal

Create a simple php script with the xdebug_break() call:

<?php
// test.php
xdebug_break(); // causes a debugger connection attempt if debugging is enabled
echo "Successful execution\n";

Run the script:

php test.php

In another terminal (inside the docker container) watch your /tmp/xdebug.log

tail -f /tmp/xdebug.log

If you see the messages bellow, it means your configuration is correct:

[562] Log opened at 2025-03-13 14:36:40
[562] I: Connecting to configured address/port: host.docker.internal:9000.
[562] W: Creating socket for 'host.docker.internal:9000', poll success, but error: Operation now in progress (29).
[562] E: Could not connect to client. :-(
[562] Log closed at 2025-03-13 14:36:40

Don’t be afraid of this Could not connect to client. 🙁. It just notifies that no one (aka IDE) listens us yet.

Checking without XDEBUG_CONFIG

Remove XDEBUG_CONFIG from your Docker container:

unset XDEBUG_CONFIG

Try to run the script once more

php test.php

If there are no remote debugging entries in the logs, this confirms that the CLI process does not enable debugging without XDEBUG_CONFIG.

IDE configuration

PHPStorm

  1. Go to the Settings (Ctrl+Alt+S) Languages & Frameworks > PHP > Debug > xDebug (screenshot)

    • Debug port – 9000

      Debug port must be equal to xdebug.remote_port=9000 in .docker/php/Dockerfile file

  2. Go to the Settings (Ctrl+Alt+S) Languages & Frameworks > PHP > Servers

  3. Press green "+" button, enter next parameters (screenshot), all options are important:

    • Name – app

      Name must be equal to PHP_IDE_CONFIG: "serverName=app" environment variable in docker-compose.yml, but you can use any name, app is only as example.

    • Host – localhost
    • Port – 8080
    • Debugger – Xdebug
    • Check Use path mappings…: [path/to/project/root-dir] -> /var/www/html.

      Hint: To submit the "absolute path on the server" press Enter after typing the path in the text field. If you only click out of the field, your input will be removed.

  4. Press "Apply" and "OK";

  5. Reload Docker with:

    $ ./bin/docker-compose down
    $ ./bin/docker-compose up
  6. Set breakpoint in your code

  7. Press Start listening for PHP Debug Connection in PHPStorm

  8. Open project in Web browser on http://localhost:8080.

VSCode

Before integrate VSCode with xDebug I recommend you install the PHP Debug and PHP IntelliSense extensions from Felix Becker and PHP Intelephense extension from Ben Mewburn. Strictly speaking, you can debug without the PHP IntelliSense and PHP Intelephense extensions, but it’s very nice to have.

For setup xDebug click Debug icon on the left panel and then click Open launch.json
file

Past next configuration to it

// launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "log": true,
            "externalConsole": false,
            "pathMappings": {
                "/app": "${workspaceFolder}"
            },
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9000
        }
    ]
}

You need to figure out the right pathMappings for your setup as not all the PHP containers you find in the docker registry specify /app as the root path. Just check out what your webroot’s path is and map that to ${workspaceFolder} assuming that you launched VSCode from your applications root.

NOTE: in pathMappings, the left part is path in Docker container, and the right part is the path on your local machine.

Next you can start debugging your application by defining a breakpoint at a section you want to take a closer look at and starting your previously defined Listen for XDebug script by clicking the small play button on the top left corner in VSCode.
You can define breakpoints by clicking right next to the line numbers in an open .php file as a red dot appears.

Open project in Web browser on http://localhost:8080.

Useful links

Leave a Reply

Your email address will not be published. Required fields are marked *