Symfony et Docker : le duo parfait
Docker et Symfony forment une combinaison puissante pour le développement. Chez Keytchens, plateforme food-tech de gestion de commandes en temps réel, cette stack a permis d'unifier les environnements de développement pour toute l'équipe.
Ce guide décrit un environnement de développement complet : PHP-FPM, Nginx, MySQL, Redis et RabbitMQ, pilotés par Docker Compose et un Makefile. L'objectif est simple : un nouveau développeur clone le dépôt, lance une commande, et obtient une application fonctionnelle, identique à celle de ses collègues.
Pourquoi conteneuriser l'environnement de développement
Sans conteneurs, chaque poste accumule sa propre version de PHP, ses extensions, son serveur MySQL et ses réglages. Les écarts finissent toujours par produire le fameux « ça marche chez moi ». Docker règle ce problème en décrivant l'environnement sous forme de code versionné avec l'application :
- la version de PHP et la liste des extensions sont fixées dans un Dockerfile ;
- les versions de MySQL, Redis et RabbitMQ sont celles de la production, pas celles installées par hasard sur le poste ;
- plusieurs projets aux besoins incompatibles (PHP 7.4 et PHP 8.3, par exemple) cohabitent sans conflit ;
- une mise à jour de l'environnement passe par une pull request, relue comme n'importe quel code.
Docker Compose pour Symfony
Le fichier compose.yaml (ou docker-compose.yml) déclare l'ensemble des services :
services:
php:
build: .docker/php
volumes:
- .:/var/www/html
depends_on:
- mysql
- redis
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- .:/var/www/html
- .docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: symfony
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7-alpine
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "15672:15672"
volumes:
mysql-data:
Quelques points à comprendre dans ce fichier :
- Le bind mount
.:/var/www/htmlmonte le code source du poste dans les conteneursphpetnginx: toute modification est visible immédiatement, sans reconstruire l'image. - Le volume nommé
mysql-dataconserve les données de la base entre deuxdocker compose down. Seuldocker compose down -vle supprime. - Le réseau est créé automatiquement : chaque service est joignable par son nom (
mysql,redis,rabbitmq). Depuis Symfony, on n'utilise donc jamaislocalhostpour atteindre la base. - Le port 15672 expose l'interface d'administration de RabbitMQ sur
http://localhost:15672(identifiants par défautguest/guest).
Attendre que la base soit réellement prête
depends_on sous sa forme simple ne garantit que l'ordre de démarrage, pas la disponibilité : MySQL peut encore être en cours d'initialisation quand PHP essaie de s'y connecter. Un healthcheck combiné à la condition service_healthy règle ce problème :
services:
php:
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-proot"]
interval: 5s
timeout: 3s
retries: 10
Configuration Nginx
Le fichier .docker/nginx/default.conf reprend la configuration recommandée par la documentation Symfony. Nginx sert les fichiers statiques du dossier public/ et transmet tout le reste à PHP-FPM, joignable sous le nom php sur le port 9000 :
server {
listen 80;
server_name localhost;
root /var/www/html/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass php:9000;
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;
}
}
Le dernier bloc renvoie une erreur 404 pour tout autre fichier PHP : seul le contrôleur frontal index.php doit être exécutable.
Dockerfile PHP optimisé
FROM php:8.3-fpm
RUN apt-get update && apt-get install -y \
libicu-dev libzip-dev \
&& docker-php-ext-install intl pdo_mysql zip opcache \
&& pecl install redis xdebug \
&& docker-php-ext-enable redis xdebug
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
L'image officielle php:8.3-fpm fournit les scripts docker-php-ext-install et docker-php-ext-enable. Les bibliothèques système (libicu-dev pour intl, libzip-dev pour zip) doivent être installées avant de compiler les extensions. Les extensions PECL comme redis et xdebug se compilent avec pecl install, puis s'activent. Enfin, COPY --from=composer:2 récupère le binaire Composer depuis son image officielle, sans script d'installation.
Si vous utilisez le transport AMQP de Messenger avec RabbitMQ, il faut aussi l'extension amqp : ajoutez librabbitmq-dev aux paquets et amqp à la ligne pecl install.
Xdebug ralentit sensiblement chaque requête. Désactivez-le par défaut et activez-le seulement quand vous en avez besoin, via un fichier .docker/php/xdebug.ini copié dans l'image :
xdebug.mode=off
xdebug.client_host=host.docker.internal
xdebug.start_with_request=trigger
La variable d'environnement XDEBUG_MODE=debug, passée au conteneur, prend le pas sur xdebug.mode : vous activez le débogage sans reconstruire l'image. Sous Linux, host.docker.internal n'existe pas par défaut : ajoutez extra_hosts: ["host.docker.internal:host-gateway"] au service php.
Connecter Symfony aux services
Dans .env.local (non versionné), les DSN pointent vers les noms des services Compose :
DATABASE_URL="mysql://root:root@mysql:3306/symfony?serverVersion=8.0&charset=utf8mb4"
REDIS_URL="redis://redis:6379"
MESSENGER_TRANSPORT_DSN="amqp://guest:guest@rabbitmq:5672/%2f/messages"
Pensez à renseigner serverVersion : Doctrine l'utilise pour choisir la bonne plateforme SQL sans interroger le serveur au démarrage.
Makefile pour simplifier les commandes
Les commandes Docker sont longues et faciles à oublier. Un Makefile à la racine du projet donne à toute l'équipe le même vocabulaire :
up:
docker compose up -d
down:
docker compose down
console:
docker compose exec php php bin/console $(cmd)
test:
docker compose exec php php bin/phpunit
migrate:
docker compose exec php php bin/console doctrine:migrations:migrate -n
Usage : make up, puis make console cmd="cache:clear" ou make migrate. Attention, les recettes d'un Makefile doivent être indentées par une tabulation, pas par des espaces. Ajoutez aussi une ligne .PHONY: up down console test migrate pour que make ne confonde pas ces cibles avec des fichiers portant le même nom.
Pièges courants
- Permissions des fichiers : PHP-FPM tourne sous
www-datadans le conteneur, alors que vos fichiers appartiennent à votre utilisateur. Sivar/cacheouvar/logne sont pas inscriptibles, alignez l'UID de l'utilisateur du conteneur sur le vôtre (argument de buildUIDetusermod) plutôt que de faire unchmod 777. - Lenteur sur macOS : les bind mounts y sont plus lents que sous Linux. Activez VirtioFS dans Docker Desktop et évitez de monter des dossiers inutiles.
- Composer hors du conteneur : lancez
composer installdans le conteneur, sinon les dépendances seront résolues pour la version de PHP du poste, pas celle de l'image. - Mots de passe en clair :
root/rootest acceptable en local uniquement. Ne réutilisez jamais ce fichier Compose tel quel en production. - Image de production : cette image embarque Xdebug et monte le code en volume. Pour la production, construisez une image distincte (build multi-stage) avec le code copié,
composer install --no-devet OPcache configuré sans vérification des timestamps.
Les bénéfices au quotidien
- Environnement identique pour tous les développeurs
- Isolation complète des services
- Facilité d'onboarding pour les nouveaux développeurs
- Versions des services alignées sur la production
- Changements d'environnement relus et versionnés comme le code
Checklist de démarrage
- Écrire le
compose.yaml, le Dockerfile PHP et la configuration Nginx. - Ajouter un
healthchecksur MySQL et conditionner le démarrage de PHP. - Désactiver Xdebug par défaut et l'activer à la demande.
- Renseigner les DSN dans
.env.localavec les noms des services. - Documenter les commandes dans un Makefile et dans le README.
- Vérifier qu'un
git clonesuivi demake upsuffit à démarrer le projet sur un poste vierge.
Avec cette base, l'environnement de développement devient un élément du projet à part entière : reproductible, documenté et facile à faire évoluer.