PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Docker from scratch – Dockerfile, nginx, docker-compose, Xdebug 3.x

by Henryk Tews / Tuesday, 07 April 2020 / Published in Środowiska

DDEV works great day to day, but what if you need a custom configuration DDEV does not support? Or you want to understand what is happening under the hood? I show how to build a PHP environment from scratch with pure Docker and docker-compose – nginx, PHP-FPM, MySQL and Redis with no intermediate tools.

Project structure

project/
  docker/
    nginx/
      default.conf
    php/
      Dockerfile
      php.ini
  src/           <- application code
  docker-compose.yml

Dockerfile for PHP-FPM

FROM php:7.4-fpm

RUN apt-get update && apt-get install -y \
    libpng-dev libjpeg-dev libfreetype6-dev libzip-dev libicu-dev \
    libxslt-dev libonig-dev git unzip \
    && rm -rf /var/lib/apt/lists/*

RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        gd pdo_mysql zip intl xsl soap bcmath sockets opcache

RUN pecl install xdebug-2.9.8 && docker-php-ext-enable xdebug

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html
RUN usermod -u 1000 www-data
USER www-data

PHP configuration – php.ini

[PHP]
memory_limit = 2G
max_execution_time = 600
upload_max_filesize = 64M
post_max_size = 64M
date.timezone = Europe/Warsaw

[opcache]
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 60000
opcache.validate_timestamps = 1

[xdebug]
xdebug.mode = debug
xdebug.client_host = host.docker.internal
xdebug.client_port = 9003
xdebug.start_with_request = no
xdebug.idekey = PHPSTORM

nginx configuration

upstream fastcgi_backend {
    server php:9000;
}

server {
    listen 80;
    server_name localhost;
    root /var/www/html/pub;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ ^/index\.php$ {
        fastcgi_pass   fastcgi_backend;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_param  MAGE_MODE developer;
    }

    location ~ /\. {
        deny all;
    }
}

docker-compose.yml – putting it all together

version: '3.8'

services:
  nginx:
    image: nginx:1.18-alpine
    ports:
      - "80:80"
    volumes:
      - ./src:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on: [php]

  php:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    volumes:
      - ./src:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
    environment:
      - XDEBUG_MODE=off
    depends_on: [db, redis]

  db:
    image: mariadb:10.4
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: magento
      MYSQL_USER: magento
      MYSQL_PASSWORD: magento
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"

  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data

volumes:
  db_data:
  es_data:

Basic commands

# Build images and start
docker-compose up -d --build

# Enter the PHP container
docker-compose exec php bash

# Run a Magento command
docker-compose exec php bin/magento setup:upgrade

# Enable Xdebug for a session
docker-compose exec php bash -c "XDEBUG_MODE=debug php bin/magento ..."

# Nginx logs
docker-compose logs -f nginx

# Stop everything
docker-compose down

# Stop and remove volumes (clean reset)
docker-compose down -v

Xdebug 3.x and PHPStorm

Xdebug 3.x renamed configuration directives from 2.x. Instead of remote_enable we now have xdebug.mode. In PHPStorm set up a PHP server with a path mapping between your local ./src directory and /var/www/html inside the container. Without this mapping breakpoints will not hit the right lines.

When to use pure Docker instead of DDEV?

Pure Docker makes sense when: you need custom service versions DDEV does not support, you are building a CI/CD environment that should be identical to local, you need full control over container networking, or the project has specific infrastructure requirements (e.g. custom reverse proxy, multiple domains on one stack).

Summary

Pure Docker requires more configuration work than DDEV, but gives full control and deeper understanding of the environment. It is worth going through this process at least once – then the DDEV abstraction stops being a black box and becomes a conscious convenience choice.

About Henryk Tews

What you can read next

DDEV – local Magento 2 in 10 minutes, comparison with XAMPP
Kubernetes for the PHP developer – kubectl debugging, Deployment YAML, HPA, troubleshooting
OpenTelemetry – distributed tracing, auto-instrumentation, Jaeger in DDEV

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}