Setting Up Xdebug with Docker Compose and PhpStorm
"Xdebug isn't connecting" in a containerized setup is almost never one big problem — it's three small settings that all have to agree with each other: where Xdebug sends its connection, how PhpStorm recognizes that connection as one it should handle, and how PhpStorm maps the container's file paths back to your actual project on disk.
1. client_host: getting out of the container
Xdebug running inside the PHP container has to reach out to your IDE listening on your host machine — the opposite direction of a normal request. host.docker.internal is the special DNS name Docker provides for exactly this:
# docker/php/xdebug.ini xdebug.mode=develop,debug xdebug.client_host=host.docker.internal xdebug.client_port=9003 xdebug.start_with_request=yes
On Docker Desktop (Mac/Windows) this resolves automatically. On Linux, add it explicitly:
services:
php:
build:
context: .
dockerfile: docker/php/Dockerfile
extra_hosts:
- "host.docker.internal:host-gateway"Both of these are exactly what's generated when you enable Xdebug on this site — see Docker Compose vs Dockerfile for why Xdebug specifically requires switching the PHP service from a plain image to a custom-built one.
2. The IDE key: telling PhpStorm this request is a debug session
Every debug-enabled request carries an IDE key (PhpStorm's default is PHPSTORM) so the listener knows to intercept it rather than ignore it as ordinary traffic. With xdebug.start_with_request=yes set above, Xdebug attaches to every request automatically without needing a browser extension or query parameter — simpler for containerized setups, at the cost of a small amount of overhead on every request, which is exactly why it should stay a dev-only setting.
In PhpStorm: Run → Start Listening for PHP Debug Connections (the little phone-handset icon) has to be toggled on before any of this does anything.
3. Path mappings: the step everyone forgets
Inside the container, your project lives at /var/www/html. On your machine, it lives wherever you cloned the repo. If PhpStorm doesn't know these two paths are the same project, it will accept the debug connection and simply fail to bind any breakpoints — no error, they just don't stop.
Fix it under Settings → PHP → Servers: add a server matching your project (name doesn't matter, but note it), then map the absolute path on the server side (/var/www/html) to your project root on the local side. PhpStorm usually prompts for this mapping automatically the first time it receives a debug connection from an unrecognized path — if it doesn't, this is the dialog to configure manually.
Verifying it works
Set a breakpoint on the first line of your front controller (public/index.php), start listening, and load any page. If it doesn't stop:
- Confirm the container actually built with Xdebug —
docker compose exec php php -m | grep -i xdebugshould printxdebug. - Confirm PhpStorm's listener is toggled on, not just configured.
- Re-check the path mapping — this is the single most common cause once the connection itself is established.
Turn it off when you're not using it
Xdebug adds real per-request overhead. Keep the Xdebug-enabled build for local development only, and use the plain image:-based PHP service (no Dockerfile, no Xdebug) for anything closer to production.
Generate a ready-to-run Docker Compose setup for your stack — compose.yml, nginx config, .env, and a README, in seconds.
Open the generator →