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 rulesudo 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 havexdebug.remote_host=host.docker.internal
in yourphp.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
-
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
- Debug port – 9000
-
Go to the Settings (Ctrl+Alt+S) Languages & Frameworks > PHP > Servers
-
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 indocker-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.
- Name – app
-
Press "Apply" and "OK";
-
Reload Docker with:
$ ./bin/docker-compose down $ ./bin/docker-compose up
-
Set breakpoint in your code
-
Press
Start listening for PHP Debug Connection
in PHPStorm -
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
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.