to run stably
Xdebug with Docker is not a one-click setup: host.docker.internal, port 9003, path mappings, the correct xdebug.mode, and PHPStorm server configuration all have to line up. This article systematically puts every piece into the right position, for stable breakpoints in Magento 2 and other PHP projects.
Table of Contents
- 1. How Xdebug, Docker, and PHPStorm work together
- 2. xdebug.ini: the complete configuration for Docker
- 3. Linux-specific issue: configuring host-gateway
- 4. PHPStorm server configuration and path mappings
- 5. Enabling debug listening and testing the first breakpoints
- 6. CLI debugging: debugging Magento commands
- 7. Xdebug and performance: enable only when needed
- 8. Common errors and their solutions, compared
- 9. Summary
- 10. FAQ
1. How Xdebug, Docker, and PHPStorm work together
To configure Xdebug with Docker and PHPStorm stably, you need to understand the communication architecture. Xdebug is a PHP extension that runs inside the PHP container. When PHP reaches a breakpoint, Xdebug initiates a TCP connection back to the IDE, not the other way around. That means the container needs to be able to reach the developer machine (the host). PHPStorm listens on a port (9003 by default in Xdebug 3) for incoming Xdebug connections.
This direction of the connection is the core of the Docker problem. Inside the container, the host is not automatically reachable. On macOS and Windows, Docker Desktop provides host.docker.internal, a DNS name that points to the host IP. On Linux, this name does not exist by default. There it has to be defined explicitly via extra_hosts in the compose configuration: host.docker.internal:host-gateway. host-gateway is a Docker-internal alias for the gateway IP of the Docker bridge network, which is typically the host's IP within the container network.
2. xdebug.ini: the complete configuration for Docker
The Xdebug configuration is defined in a separate xdebug.ini file that gets included as a PHP extension configuration. The most important settings for Xdebug 3 in Docker: xdebug.mode=debug enables the step debugger. xdebug.start_with_request=yes starts Xdebug automatically on every PHP request. xdebug.client_host=host.docker.internal specifies the address Xdebug connects to. xdebug.client_port=9003 is the default port for Xdebug 3 (Xdebug 2 used 9000).
The setting xdebug.start_with_request=yes means Xdebug starts on every request, even when PHPStorm is not waiting for a connection. That leads to a brief timeout on every request when PHPStorm is not listening. An alternative is xdebug.start_with_request=trigger: Xdebug only starts when a special cookie, header, or GET parameter is present. That is better for day-to-day performance, but requires browser extensions or manual headers for the trigger.
<?php
// docker/phpfpm/xdebug.ini - Xdebug 3 for Docker + PHPStorm
/*
[xdebug]
; Core settings
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_port=9003
xdebug.client_host=host.docker.internal
xdebug.idekey=PHPSTORM
; Logging (disable in production, enable for troubleshooting)
xdebug.log_level=0
; xdebug.log=/tmp/xdebug.log
; Magento-specific: increase nesting level
xdebug.max_nesting_level=512
; Performance: disable coverage when not needed
; Set to 'debug,coverage' only when running PHPUnit with coverage
; For trigger-mode (better performance, needs browser extension):
; xdebug.start_with_request=trigger
; xdebug.trigger_value=PHPSTORM
*/
// Verify Xdebug is loaded in container:
// bin/cli php -v | grep Xdebug
// Expected: Xdebug v3.x.x, Copyright (c) ...
// Check which settings are active:
// bin/cli php -r "var_dump(xdebug_info());"
3. Linux-specific issue: configuring host-gateway
The Linux-specific problem with Xdebug and Docker is the most common obstacle for PHP developers who switch from macOS to Linux or use Linux as their development platform. The Mark Shust Docker setup has the file compose.dev-linux.yaml for this case, which contains Linux-specific configurations. Alternatively, you can add the extra_hosts flag directly to the main compose.yaml if only Linux is used as the development platform.
After adding extra_hosts: - "host.docker.internal:host-gateway" to the phpfpm service, the container needs to be restarted. To verify: inside the container, run ping host.docker.internal or curl -v telnet://host.docker.internal:9003 while PHPStorm is currently waiting for connections. A response (even a connection refusal, as long as PHPStorm is not listening) shows that DNS resolution works. If the ping fails, extra_hosts is not configured correctly.
<?php
// compose.dev-linux.yaml - Linux-specific Xdebug fix
/*
services:
phpfpm:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
PHP_IDE_CONFIG: "serverName=mironsoft-docker"
XDEBUG_CONFIG: "client_host=host.docker.internal"
# Use with: docker compose -f compose.yaml -f compose.dev-linux.yaml up -d
# Alternative: add directly to compose.yaml if Linux-only setup
# Verification steps:
# 1. Start containers: bin/start (or docker compose up -d)
# 2. Enter container: bin/bash
# 3. Test connectivity: ping -c 1 host.docker.internal
# 4. Should resolve to Docker bridge IP (172.17.0.1 or similar)
# 5. Enable PHPStorm debug listening (phone icon in toolbar)
# 6. Test: nc -zv host.docker.internal 9003
# Should show: Connection to host.docker.internal 9003 port [tcp/*] succeeded
*/
4. PHPStorm server configuration and path mappings
On the PHPStorm side, two configuration areas are relevant for Xdebug: the debug settings and the server configuration. The debug settings are found under Settings → PHP → Debug: set the Xdebug port to 9003, enable "Can accept external connections". The server configuration under Settings → PHP → Servers defines how PHPStorm maps file paths from the Xdebug protocol onto local files.
The server configuration: Settings → PHP → Servers → +. Name: mironsoft-docker (must match the value in PHP_IDE_CONFIG=serverName=mironsoft-docker). Host: localhost. Port: 80. Debugger: Xdebug. Enable "Use path mappings". Then map the local project directory /home/mir/development/mironsoft/src to the container path /var/www/html. This mapping step is critical: without it, PHPStorm does not open a local file when a breakpoint is hit, and instead shows a "Cannot open file" error.
<?php
// PHPStorm Server Configuration Summary
// Settings → PHP → Servers
/*
Server Settings:
- Name: mironsoft-docker
(matches PHP_IDE_CONFIG="serverName=mironsoft-docker" in container)
- Host: localhost
- Port: 80 (or 443 for HTTPS)
- Debugger: Xdebug
Path Mappings (Use path mappings ✓):
Local Path → Absolute path on server
/home/mir/development/mironsoft/src → /var/www/html
Settings → PHP → Debug:
- Debug port: 9003
- ✓ Can accept external connections
- ✓ Force break at first line when no path mapping specified
- ✓ Force break at first line when a script is outside the project
// Environment variable in container (compose.yaml):
// environment:
// PHP_IDE_CONFIG: "serverName=mironsoft-docker"
// PHPStorm uses PHP_IDE_CONFIG to auto-select the server
// when a new Xdebug session arrives
*/
5. Enabling debug listening and testing the first breakpoints
Once all settings are configured, the workflow for a debugging session is: put PHPStorm into "Listen" mode (phone icon in the top toolbar or Run → Start Listening for PHP Debug Connections). Then open the Magento page that should be debugged in the browser. If xdebug.start_with_request=yes is active, Xdebug connects automatically. If trigger mode is active, the Xdebug cookie or header must be set (via a browser extension like "Xdebug Helper").
On the first successful connection, PHPStorm either shows the Incoming Connection dialog (if PHPStorm doesn't know which server to use) or halts directly at the first breakpoint. If the dialog appears, select the server there and path mappings can be confirmed directly. A common first test: set a breakpoint in index.php (the Magento entry point) and open any frontend page. If PHPStorm halts there, the entire setup is working correctly.
6. CLI debugging: debugging Magento commands
Xdebug works not only for web requests but also for CLI commands. This is especially useful for Magento, because many core processes run as CLI commands: setup:upgrade, indexer:reindex, data migration, and custom CLI commands. To debug a CLI command, PHP_IDE_CONFIG has to be set in the shell environment and Xdebug has to be enabled for CLI.
The Mark Shust setup has the script bin/debug-cli enable for this, which activates the necessary PHP configuration for CLI debugging. Alternatively, set the XDEBUG_SESSION environment variable directly: XDEBUG_SESSION=PHPSTORM bin/magento cache:flush. PHPStorm has to be in listen mode. The CLI process inside the container then connects to PHPStorm via host.docker.internal:9003. Path mappings have to be correct, identical to those for web requests.
7. Xdebug and performance: enable only when needed
Xdebug carries a significant performance overhead. With Xdebug enabled (even without an active debugging session), PHP runs slower: the overhead from instrumenting every function call and checking for debug sessions is, depending on the application, 10 to 50 percent more execution time. For Magento 2, which is resource-intensive to begin with, that is especially noticeable: pages load noticeably slower, CLI commands take longer.
The recommended strategy: don't leave Xdebug permanently enabled. The Mark Shust setup has bin/xdebug enable and bin/xdebug disable, scripts that enable or disable Xdebug in the container by changing the PHP configuration without a container restart. Alternatively, use trigger mode: xdebug.start_with_request=trigger. Then PHP runs without overhead until an explicit trigger cookie or header is set. The browser extension "Xdebug Helper" makes setting the trigger possible with a single click.
8. Common errors and their solutions, compared
| Symptom | Cause | Solution | Verification |
|---|---|---|---|
| PHPStorm is listening, but the breakpoint is never hit | host.docker.internal is not resolvable (Linux) | extra_hosts: host.docker.internal:host-gateway | Inside the container: ping host.docker.internal |
| PHPStorm does not open a file at the breakpoint | Path mappings are missing or wrong | Settings → PHP → Servers → Path Mappings | Local: /src → Container: /var/www/html |
| Xdebug connects, but the wrong server | PHP_IDE_CONFIG serverName is missing | PHP_IDE_CONFIG=serverName=mironsoft-docker | Incoming Connection dialog shows server selection |
| Port 9003 is occupied, connection fails | Another process is using port 9003 | ss -tulpn | grep 9003, terminate the process | PHPStorm: Settings → PHP → Debug → Port |
| Xdebug is active, but there is no connection | PHPStorm is not in listen mode | Run → Start Listening for PHP Debug Connections | Phone icon in the toolbar is green/active |
9. Summary
Configuring Xdebug with Docker and PHPStorm stably requires the correct interplay of four components: the Xdebug INI inside the container (mode, client_host, client_port), Docker network configuration (host-gateway on Linux), PHPStorm server configuration with path mappings, and enabled debug listening mode. When all four parts are correct, debugging works reliably, for web requests, GraphQL requests, and CLI commands alike.
Xdebug with Docker and PHPStorm: the essentials at a glance
xdebug.ini
mode=debug, start_with_request=yes, client_host=host.docker.internal, client_port=9003, max_nesting_level=512.
Linux: host-gateway
extra_hosts: host.docker.internal:host-gateway in the phpfpm service. Restart the container. Verification: ping host.docker.internal inside the container.
PHPStorm Server
Settings → PHP → Servers. Name = serverName in PHP_IDE_CONFIG. Path mapping: local /src → container /var/www/html.
Performance
Enable Xdebug only when needed. bin/xdebug enable|disable or trigger mode. Browser extension for the trigger cookie.