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 miss-configuration 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.

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

<?php

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

    return $textHello . " " . $name;
}

echo sayHello("world");

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

FROM php:8.0-fpm-alpine

RUN apk add --no-cache --update --virtual \
    build-base \
    gcc \
    g++ \
    autoconf \
    make

RUN pecl install xdebug-3.0.4 \
    && docker-php-ext-enable xdebug \
    && echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.discover_client_host=1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.idekey=PHPSTORM" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

WORKDIR '/var/www/html'

EXPOSE 80 9000 9003

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

I use the php:8.0-fpm-alpine as my base image. I prefer Alpine Linux since it generates smaller Docker images but any other Linux distribution should work.

Then I install some needed dependencies and using pecl I install and enable Xdebug. Finally I expose some ports :80 for the web server, 9000 and 9003 for Xdebug. Lastly I start the PHP local development server php -S 0.0.0.0:80.

Εven though my example is very simple and I could just use a single Dockerfile, I use Docker Compose because the result it is more readable and a lot easier to configure than a Docker command:

version: "3.9"
services:
  php:
    build: ./
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ".:/var/www/html"

Next, I place some breakpoints using PHPStorm, and finally I visit the endpoint on my localhost on port 80 to trigger the execution of the script and Xdebug respectively:

docker config

On the first request, PHPStorm will ask for path mapping configuration, which is a mapping between the location of the app in the host machine and the container, the defaults usually work:

docker config

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

Depending on your setup and configuration, your Xdebug and PHPStorm settings might need to be adjusted. Useful information can be found on the PHPStorm and Xdebug documentations.