Unlike other ecosystems like .NET or Java in which containerizing an application for local development might seem more like a burden than a feature when it comes to PHP this is a necessity as well as a welcome bonus of mirroring the production environment very closely. Some applications might require a PHP extension that is not present on the local system or there might be some specific php.ini configuration that is set on the production server, which is considered as part of the infrastructure rather than the application itself but can cause problems because of the inconsistency (I had this happen to me several years ago). Using Docker can solve these types of problems and has also the added benefit of being a really good way to develop applications, an argument for this being the good support for remote debugging in IDEs. Overview I will go through setting up the entire stack, that is PHP 8.1 with fpm, Nginx, and MySQL as the database and at the end, we will do basic Symfony installation to test everything together. If you don't just want to check out the repo with the entire setup you can find it on Github Setting up docker-compose configuration There are two files that tell docker-compose what to spin up. The first is docker-compose.yml and the other is docker-compose.override.yml. The override file is responsible for exposing the host using the hostname . This is useful for both debugging and for referencing other containers more easily. dockerhost version: '3.5' services: devbox: container_name: devbox-nginx build: context: ./docker/nginx dockerfile: Dockerfile ports: - "9001:80" volumes: - .:/app:cached restart: unless-stopped depends_on: - devbox-service devbox-service: container_name: devbox-service build: context: . volumes: - .:/app:cached - ./docker/service/php.ini:/usr/local/etc/php/conf.d/99-app.ini - ./docker/service/www.conf:/usr/local/etc/php-fpm.d/www.conf restart: unless-stopped environment: XDEBUG_CONFIG: ${XDEBUG_CONFIG} APP_ENV: ${APP_ENV} APP_DEBUG: ${APP_DEBUG} APP_SECRET: ${APP_SECRET} env_file: - .env - .env.local depends_on: - mysql mysql: image: mysql:8.0 container_name: devbox-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: database ports: - "3308:3306" volumes: - database-volume:/var/lib/mysql volumes: database-volume: driver: "local" Adding Dockerfiles There are two Dockerfiles used. One for php-fpm and the other for Nginx. The one for the app itself is the php-fpm one and it's found at the root of the project and the other one is found in . docker/nginx/Dockerfile The Nginx Dockerfile just copies the Nginx configuration defined in default.conf into the container. The Nginx configuration is quite apart from the fact that the name of the service defined in docker-compose was specified Dockerfile for Nginx: FROM nginx:stable COPY default.conf /etc/nginx/conf.d/default.conf Dockefile for php-fpm: FROM php:8.1-fpm-alpine LABEL maintainer="alexandrunastase@github" LABEL description="Devbox Docker image" # User build args ARG APP_ENV="prod" ARG APP_DEBUG="0" ARG APP_LOG="php://stdout" # Environment variables ENV APP_ENV=${APP_ENV} ENV APP_DEBUG=${APP_DEBUG} ENV APP_LOG=${APP_LOG} ENV XDEBUG_CONFIG="" ENV COMPOSER_NO_INTERACTION=1 # Add PHP user ARG PHP_USER_ID=1000 ARG PHP_GROUP_ID=1000 RUN set -x \ && addgroup -g $PHP_GROUP_ID -S php \ && adduser -u $PHP_USER_ID -D -S -G php php # Install dependencies RUN set -ex \ && docker-php-source extract \ && apk add --update --no-cache \ ${PHPIZE_DEPS} \ curl \ # Runtime deps icu-dev icu-libs \ libzip-dev zlib-dev \ libxml2-dev \ oniguruma-dev \ && pecl install xdebug \ && docker-php-ext-install intl opcache pdo_mysql zip bcmath mbstring sockets pcntl soap sockets ctype > /dev/null \ && docker-php-ext-enable intl opcache pdo_mysql zip bcmath mbstring sockets pcntl soap sockets ctype \ && apk del ${PHPIZE_DEPS} \ && docker-php-source delete # Copy configuration files COPY ./docker/service/www.conf /usr/local/etc/php-fpm.d/www.conf COPY ./docker/service/php.ini $PHP_INI_DIR/conf.d/99-app.ini COPY ./docker/service/xdebug.ini $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini # Install composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer COPY --chown=php . /app WORKDIR /app USER php Creating a Makefile I found a Makefile a useful addition to any docker-compose setup, as it makes it much easier to access common commands without needing to remember how a container was named or needed to search the shell history. When editing Makefiles make sure to always use tabs instead of spaces, especially when indenting commands. .PHONY: run run: @if [ ! -e ".env.local" ]; then\ cp .env .env.local; \ fi @docker-compose up -d @echo "Service is running on http://localhost:9001" .PHONY: install install: @docker-compose exec --user="php" -T devbox-service composer install .PHONY: stop stop: @docker-compose stop .PHONY: enter enter: @docker-compose exec --user="php" devbox-service /bin/sh .PHONY: enter-as-root enter-as-root: @docker-compose exec --user="root" devbox-service /bin/sh .PHONY: test test: @docker-compose exec --user="php" -T devbox-service /bin/sh -c 'APP_ENV="test" ./bin/phpunit --testdox' .PHONY: destroy destroy: @docker-compose down --rmi local Adding Symfony and testing everything To test the entire setup we can set up a Symfony application. You can find instructions to do so . I went with the LTS version which is 5.4 at the time of writing. here I also updated the composer file to make sure the database is created. You can skip this if there's another way to make that happen. Setting up xDebug Debugging can be enabled by uncommenting the contents of the file ./docker/service/xdebug.ini These are the steps to configure xDebug on PHPStorm: Choose as CLI interpreter. Make sure the local interpreter is removed PHP Remote Debugging Choose as the configuration type and as the service Docker Compose devbox-service Lifecycle should be Connect to existing container Working demo In the docker-compose the port is mapped to the localhost so you can check if everything is working after running: 9001 make run To set up the containers: make install to install all the composer packages. There is one endpoint defined call which should return a 200 status code. http://localhost:9001/healthz In order to run the tests, you can run: make test To run other ad-hoc commands like requiring another composer package you can do: make enter Tested using Ubuntu 21.10 docker version : 20.10.14 docker-compose version : 1.29.1 Also published . here