Skip to content
AchillesBackend developer

Xdebug 3 with Docker Compose and PhpStorm

2021updated 20265 min read#php#docker

ON THIS PAGE

Step debugging PHP in Docker. A minimal Docker Compose setup with Xdebug 3, the PhpStorm settings it needs, and fixes for when it won't connect.

I have used many development environments in the past, from the local LEMP stack to Virtual Machines and Vagrant etc. There were always issues among the machines. Sometimes due to minor differences in the programming language or the database version, other times due to misconfiguration or a mistake during the set-up.

Docker solves many of those problems but in a slightly different way. For the sake of simplicity I will keep my examples as minimal as possible, thus many aspects of a real development environment are missing. For a complete setup with FrankenPHP, Caddy and Symfony, see my php-docker (opens in a new tab) repo, which ships with Xdebug ready to use.

First of all, the code! Nothing fancy, just a simple “Hello World” script:

php
<?php

function sayHello($name) {
    $textHello = "Hello";

    return $textHello . " " . $name;
}

echo sayHello("world");

Dockerfile

To build my PHP Docker image, I am using the following Dockerfile:

Dockerfile
FROM php:8.4-cli-alpine

COPY --from=ghcr.io/mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/

RUN install-php-extensions xdebug \
    && echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.start_with_request=trigger" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

WORKDIR /var/www/html

CMD ["php", "-S", "0.0.0.0:80"]

I use the php:8.4-cli-alpine as my base image. I prefer Alpine Linux since it generates smaller Docker images but any other Linux distribution should work. It is the CLI image because the example runs PHP’s built-in web server. If you run PHP-FPM behind Nginx or Caddy, use the matching -fpm image instead.

Then I install Xdebug with install-php-extensions (opens in a new tab), which takes care of the build dependencies and picks the Xdebug version that fits the PHP version. If you prefer pecl, apk add --no-cache --virtual .build-deps $PHPIZE_DEPS linux-headers && pecl install xdebug && docker-php-ext-enable xdebug && apk del .build-deps does the same job.

Two settings go into the Xdebug ini file:

  • xdebug.client_host=host.docker.internal tells Xdebug where PhpStorm is: on the host machine, outside the container.
  • xdebug.start_with_request=trigger starts a debug session only when you ask for one, so normal requests stay fast.

There is no EXPOSE for Xdebug. Xdebug connects out of the container to PhpStorm on port 9003, so nothing has to be opened on the container for it.

Lastly I start the PHP local development server php -S 0.0.0.0:80.

Compose

Even though my example is very simple and I could just use a single Dockerfile, I use Docker Compose because the result is more readable and a lot easier to configure than a Docker command. The file is compose.yaml, and it no longer needs a version: line (Docker Compose ignores it and warns about it):

yaml
services:
  php:
    build: .
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/html
    environment:
      XDEBUG_MODE: debug # off to disable
      PHP_IDE_CONFIG: serverName=docker
    extra_hosts:
      - host.docker.internal:host-gateway
  • XDEBUG_MODE turns the step debugger on. Set it to off when you don’t need it, without rebuilding the image.
  • PHP_IDE_CONFIG tells PhpStorm which server configuration (and path mappings) to use. It matters most for CLI scripts, which have no host name.
  • extra_hosts makes host.docker.internal work on Linux. Docker Desktop on Mac and Windows defines it already, Docker Engine on Linux does not.

Start it with docker compose up -d --build and the script answers on http://localhost:8080.

PhpStorm

First, create a server configuration under Settings → PHP → Servers:

  • Name: docker, the same as in PHP_IDE_CONFIG
  • Host: localhost, port 8080, debugger Xdebug
  • Check Use path mappings and map the project folder to /var/www/html

The path mapping is the link between the location of the app in the host machine and the container. Without it PhpStorm cannot match the files Xdebug reports to the files you have open.

Then click Start Listening for PHP Debug Connections in the toolbar (or in the Run menu).

Next, I place some breakpoints in index.php, and finally I visit the endpoint on my localhost on port 8080 with a trigger, so that Xdebug starts a session:

Xdebug should stop the execution of the script on the first breakpoint and now you should be able to debug.

CLI scripts work the same way, with the trigger as an environment variable:

bash
docker compose exec -e XDEBUG_TRIGGER=1 php php index.php

Troubleshooting

When the breakpoints are ignored, I go through these checks in order.

Is Xdebug loaded? The version should be in the output:

bash
docker compose exec php php -v
# with Xdebug v3.x.x

Are the settings right? This shows the active mode, client_host, start_with_request and the port:

bash
docker compose exec php php --ri xdebug

Nothing connects at all.

  • Did you send a trigger? With start_with_request=trigger, a plain request runs without the debugger.
  • Is PhpStorm listening? The Start Listening toggle has to be on.
  • Is XDEBUG_MODE set to debug? With off, a trigger does nothing.
  • On Linux, is extra_hosts in place, and does your firewall allow port 9003 from the Docker networks?
  • Coming from Xdebug 2? The default port changed from 9000 to 9003, and the xdebug.remote_* settings are now xdebug.client_* (see the upgrade guide (opens in a new tab)).

It connects, but the breakpoints are skipped. The path mapping is wrong, or PHP_IDE_CONFIG does not match the server name in PhpStorm.

Still stuck? Turn on the Xdebug log for one request. It says exactly where Xdebug tried to connect:

bash
docker compose exec -e XDEBUG_TRIGGER=1 -e XDEBUG_CONFIG="log=/tmp/xdebug.log log_level=3" php sh -c 'php index.php; cat /tmp/xdebug.log'
# ERR: Could not connect to debugging client. Tried: host.docker.internal:9003

Depending on your setup and configuration, your Xdebug and PhpStorm settings might need to be adjusted. Useful information can be found on the PhpStorm (opens in a new tab) and Xdebug (opens in a new tab) documentation.

Did this help?

Comments

via GitHub Discussions