Xdebug 3 step debugging inside Docker: getting it to connect back to the IDE
Every time I set up a new project I lose an afternoon to Xdebug not connecting from inside the container back to my IDE. Xdebug 3 changed the config keys, so old guides with remote_host and remote_enable just confuse things. On Linux the host is not host.docker.internal by default, and on a Swoole long-running server the request model is different from FPM.
Can we collect the actual working config for Xdebug 3 in Docker, including the Linux host gateway issue, so the next person does not lose the afternoon?
Xdebug 3 renamed everything. The minimal set is mode, client_host, and start_with_request. No more remote_enable or remote_host.
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
Note the port is 9003 now, not 9000, which collides with php-fpm anyway. start_with_request=yes means it tries on every request, use trigger if that is too aggressive.
host.docker.internal does not exist on Linux out of the box, that is the afternoon killer. Add it in compose with an extra_hosts entry mapping it to the host gateway:
extra_hosts:
- "host.docker.internal:host-gateway"
With that line the same client_host value works on Mac, Windows, and Linux. Without it your container resolves nothing and Xdebug silently fails to connect.
Debug the debugger by turning on the log. Set xdebug.log to a file and xdebug.log_level to 7, then tail it. It tells you exactly whether it is trying to connect, to what host, and whether the IDE refused. Nine times out of ten the log shows it dialing the wrong host or the IDE not listening on 9003. Stop guessing and read what Xdebug is actually doing.
The extra_hosts host-gateway line was my missing piece on Linux, and the log_level 7 trick showed me it had been dialing a host that did not resolve the whole time. Connecting now. For anyone on the same path, also make sure your IDE path mappings point the container project path to your local path or breakpoints look unverified even after the connection works.
For Swoole or any long-running server, prefer start_with_request=trigger over yes, otherwise Xdebug attaches on every single request and tanks throughput while you are not even debugging. Use a browser extension or an environment variable to set the trigger only when you want a session. And keep Xdebug out of your production image entirely, even loaded-but-idle it has overhead.
```php blocks are runnable.