Aller au contenu principal
Retour au blog

Docker pour les devs PHP : le setup qui marche vraiment

Flavien Métivier16 décembre 20255 min

Docker et PHP, c'est une histoire d'amour compliquée. Des Dockerfiles de 200 lignes, des problèmes de permissions, des performances catastrophiques sur macOS. Si tu as déjà perdu une demi-journée à debugger un container PHP-FPM, cet article est pour toi. On va monter un setup complet et testé pour le développement Symfony.

Le docker-compose.yml complet

Voici le fichier docker-compose qui fonctionne en production chez nos clients. Il couvre les quatre services essentiels : PostgreSQL, Redis, PHP-FPM et Nginx.

version: '3.8'

services:
    php:
        build:
            context: .
            dockerfile: docker/php/Dockerfile
            target: dev
        volumes:
            - .:/app:cached
            - php_socket:/var/run/php
        environment:
            APP_ENV: dev
            DATABASE_URL: postgresql://app:secret@postgres:5432/app
            REDIS_URL: redis://redis:6379
        depends_on:
            postgres:
                condition: service_healthy
            redis:
                condition: service_healthy

    nginx:
        image: nginx:1.25-alpine
        ports:
            - "8080:80"
        volumes:
            - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
            - ./public:/app/public:ro
            - php_socket:/var/run/php
        depends_on:
            - php

    postgres:
        image: postgres:16-alpine
        environment:
            POSTGRES_DB: app
            POSTGRES_USER: app
            POSTGRES_PASSWORD: secret
        ports:
            - "5432:5432"
        volumes:
            - postgres_data:/var/lib/postgresql/data
        healthcheck:
            test: ["CMD-SHELL", "pg_isready -U app"]
            interval: 10s
            timeout: 5s
            retries: 5

    redis:
        image: redis:7-alpine
        ports:
            - "6379:6379"
        volumes:
            - redis_data:/data
        healthcheck:
            test: ["CMD", "redis-cli", "ping"]
            interval: 10s
            timeout: 5s
            retries: 5

volumes:
    postgres_data:
    redis_data:
    php_socket:

Le Dockerfile multi-stage : dev vs prod

Un seul Dockerfile avec deux targets. Le stage dev inclut Xdebug et Composer. Le stage prod est optimisé pour la performance avec OPcache.

# docker/php/Dockerfile
FROM php:8.3-fpm-alpine AS base

RUN apk add --no-cache \
    icu-dev \
    libpq-dev \
    linux-headers \
    && docker-php-ext-install \
    intl \
    pdo_pgsql \
    opcache

RUN apk add --no-cache libzip-dev \
    && docker-php-ext-install zip

WORKDIR /app

# ── Stage dev ──
FROM base AS dev

RUN apk add --no-cache $PHPIZE_DEPS \
    && pecl install xdebug redis \
    && docker-php-ext-enable xdebug redis

COPY docker/php/conf.d/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
COPY docker/php/conf.d/php-dev.ini /usr/local/etc/php/conf.d/php.ini

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

# ── Stage prod ──
FROM base AS prod

RUN apk add --no-cache $PHPIZE_DEPS \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && apk del $PHPIZE_DEPS

COPY docker/php/conf.d/php-prod.ini /usr/local/etc/php/conf.d/php.ini

COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts

COPY . .
RUN composer dump-autoload --optimize --classmap-authoritative

La configuration Nginx pour PHP-FPM

# docker/nginx/default.conf
server {
    listen 80;
    server_name localhost;
    root /app/public;

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

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }

    location ~ \.php$ {
        return 404;
    }
}

Les pièges courants et leurs solutions

Envie d'aller plus loin ?

Réserver un appel découverte

Après avoir mis en place Docker sur des dizaines de projets Symfony, voici les trois problèmes les plus fréquents.

1. Permissions fichiers — Le classique : les fichiers créés par le container appartiennent à root. La solution : ajouter un utilisateur non-root dans le Dockerfile avec le même UID que ton utilisateur local. ARG UID=1000 puis RUN adduser -D -u $UID appuser.

2. Performance sur macOS — Les volumes montés sont lents sur macOS à cause de la couche de virtualisation. Deux solutions : utiliser le flag :cached sur les volumes (déjà dans notre compose) ou exclure vendor/ du montage et l'installer directement dans le container.

3. Configuration Xdebug — Xdebug doit contacter ton IDE, pas l'inverse. La configuration critique :

; docker/php/conf.d/xdebug.ini
[xdebug]
xdebug.mode=debug
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.start_with_request=trigger
xdebug.idekey=PHPSTORM

Les commandes du quotidien

# Démarrer l'environnement
docker compose up -d

# Exécuter les commandes Symfony
docker compose exec php bin/console cache:clear
docker compose exec php bin/console doctrine:migrations:migrate

# Lancer les tests
docker compose exec php vendor/bin/phpunit

# Lancer PHPStan
docker compose exec php vendor/bin/phpstan analyse --level=max

# Installer les dépendances
docker compose exec php composer install

# Arrêter et nettoyer
docker compose down -v

Ce setup couvre 90 % des besoins d'un projet Symfony. Pour les cas plus avancés — Elasticsearch, RabbitMQ, workers Messenger — il suffit d'ajouter les services dans le compose. Si tu cherches un accompagnement complet, notre offre Bear MVP inclut la configuration Docker dans chaque livraison de MVP.

Cet article vous a plu ? Partagez-le !

Envie d'aller plus loin ?

Discutons de votre projet et voyons comment je peux vous aider.