Aller au contenu principal

Symfony avec Docker

Déploiement de Symfony 7.3 avec une image Docker unique (Nginx + PHP-FPM) et MySQL 8.0

symfony-docker.png

Cette approche consiste à créer une seule image Docker qui contient l'application, PHP-FPM et le serveur Web Nginx.

Un gestionnaire de processus, supervisor, se chargera de lancer et de maintenir ces 2 services en cours d'exécution.

astuce

Cette approche simplifie le déploiement en production car il n'y a qu'un seul conteneur d'application à gérer.

Rappel

Pour les variables d'environnement dans des fichiers, Symfony utilise par défaut :

  • .env (pour les valeurs par défaut)
  • .env.local (pour les données confidentielles locales, ignoré par Git).
remarque

Si Docker n'est pas installé, veuillez consulter le tutoriel "Installation de Docker".


1. Préparation​

astuce

Afin de bien gérer les versions des fichiers de votre projet, pensez à créer une nouvelle branche avec Git.

Voici la liste des fichiers qui vont être utilisés :

remarque

Nous allons créer les fichiers nécessaires et supprimer les fichiers qui peuvent poser problème.

votre-projet-symfony/
├── docker-compose.yml
├── Dockerfile
├── entrypoint.sh
├── supervisord.conf # Configuration de Supervisor pour le Dockerfile
├── .env # Fichier commité (valeurs par défaut non sensibles)
├── .env.local # Fichier local (ignoré par Git, pour les secrets)
├── nginx.conf
└── (le reste de votre projet Symfony)
astuce

Afin de ne pas entrer en conflit avec d'autres consignes Docker, veuillez effacez les fichiers fournis par défaut par Symfony :

  • compose.yaml
  • compose.override.yaml

2. Fichier .env​

Ce fichier contient uniquement des valeurs d'exemples et est envoyé sur GitHub.

# .env (commité)
###> doctrine/doctrine-bundle ###
DATABASE_URL="mysql://symfony_user:ChangeMe!@db:3306/symfony_db?serverVersion=8.0"
###< doctrine/doctrine-bundle ###

###> symfony/framework-bundle ###
APP_ENV=prod
###< symfony/framework-bundle ###
attention

Attention, le nom du serveur de bases de données s'appelle db.

DATABASE_URL= ...@db...

Nous allons ajouter 4 variables pour la configuration de MySQL :

###> docker ###
# Mettre les mêmes valeurs que dans DATABASE_URL
MYSQL_DATABASE="symfony_db"
MYSQL_USER="symfony_user"
MYSQL_PASSWORD="ChangeMe!"
MYSQL_ROOT_PASSWORD="RootChangeMe!"
###< docker ###

3. Fichier .env.local​

Rappel

Le fichier .env.local surcharge les valeurs du fichier .env.

attention

Pensez à ajouter .env.local à votre .gitignore. Le fichier .env.local contient des valeurs sensibles et ne doit jamais être envoyé sur GitHub.

# .env.local
###> doctrine/doctrine-bundle ###
# Utilisez des valeurs uniques et sécurisées pour votre projet
DATABASE_URL="mysql://mon_user:mon_mot_de_passe_secret@db:3306/ma_base_de_donnees?serverVersion=8.0"
###< doctrine/doctrine-bundle ###

###> symfony/framework-bundle ###
APP_ENV=prod
###< symfony/framework-bundle ###
attention

Attention, le nom du serveur de bases de données s'appelle db.

DATABASE_URL= ...@db...

Nous allons ajouter ici aussi les 4 variables pour MySQL :

###> docker ###
# Mettre les mêmes valeurs que dans DATABASE_URL
MYSQL_DATABASE="ma_base_de_donnees"
MYSQL_USER="mon_user"
MYSQL_PASSWORD="mon_mot_de_passe_secret"
MYSQL_ROOT_PASSWORD="mon_mot_de_passe_root_secret"
###< docker ###

4. Dockerfile​

Ce Dockerfile installe Nginx et Supervisor, et configure l'environnement pour les deux services.

FROM php:8.3-fpm-bookworm

# Installation des dépendances système, on ajoute nginx et supervisor
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
libzip-dev \
netcat-traditional \
libicu-dev \
pkg-config \
nginx \
supervisor \
&& docker-php-ext-configure intl \
&& docker-php-ext-install pdo pdo_mysql zip bcmath intl gd \
&& rm -rf /var/lib/apt/lists/*

# Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Copie du code de l'application
WORKDIR /var/www
COPY . .

# Install des dépendances PHP (prod only)
RUN composer install --optimize-autoloader --no-interaction --no-scripts --dev

# --- Configuration de l'environnement ---

# 1. On s'assure que PHP-FPM écoute bien sur localhost, car Nginx est dans le même conteneur.
RUN sed -i "s/listen = 9000/listen = 127.0.0.1:9000/" /usr/local/etc/php-fpm.d/www.conf

# 2. On copie nos fichiers de configuration personnalisés
COPY nginx.conf /etc/nginx/sites-available/default
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# 3. On copie et on rend exécutable le script d'entrée
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh && sed -i 's/\r$//' /usr/local/bin/entrypoint.sh

# Permissions pour les dossiers de Symfony
RUN chown -R www-data:www-data /var/www

# On expose le port 80, celui de Nginx
EXPOSE 80

# On lance notre script d'entrée qui va gérer le démarrage
ENTRYPOINT ["entrypoint.sh"]

5. supervisord.conf​

Créez ce fichier.

Ce fichier indique à Supervisor quels processus (Nginx et PHP-FPM) lancer et comment les surveiller.

[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid

[program:php-fpm]
command=php-fpm
autostart=true
autorestart=true
priority=5
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

6. entrypoint.sh​

Le script effectue les tâches d'initialisation (migrations, cache) puis lance supervisor pour démarrer Nginx et PHP-FPM.

#!/bin/sh
set -e

echo "Attente MySQL..."
timeout 30s sh -c 'until nc -z db 3306 2>/dev/null; do echo "."; sleep 1; done' || (echo "DB KO" && exit 1)
echo "DB OK !"

php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration

# On charge les fixtures si on est en environnement de développement
if [ "$APP_ENV" = "dev" ]; then
echo "Chargement des fixtures de développement..."
php bin/console doctrine:fixtures:load --no-interaction
fi

# On charge les fixtures si la variable d'environnement est à "true"
if [ "$LOAD_FIXTURES" = "true" ]; then
echo "Chargement des fixtures (demandé par LOAD_FIXTURES=true)..."
# On exécute la commande dans un environnement temporairement 'dev'
# pour s'assurer que toutes les dépendances et services de dev sont bien chargés.
APP_ENV=dev php bin/console doctrine:fixtures:load --no-interaction
fi

php bin/console cache:warmup --no-debug

echo "Démarrage de Nginx et PHP-FPM avec Supervisor..."
# On ne lance pas php-fpm directement, mais supervisor
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

7. docker-compose.yml​

Le fichier est assez simple car il n'y a qu'un seul service pour l'application.

services:
db:
image: mysql:8.0
container_name: ma_bdd_mysql
restart: unless-stopped
env_file:
- .env.local # Charge les variables depuis .env.local
volumes:
- db_data:/var/lib/mysql
ports:
- "3306:3306"
healthcheck: # pour s'assurer que la BDD est prête avant de démarrer l'app
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s

app:
build: .
container_name: mon_app_symfony
restart: unless-stopped
volumes:
- .:/var/www # Pour le développement local, pour voir les changements en direct
env_file:
- .env.local # Charge les variables depuis .env.local
ports:
- "8088:80" # On expose le port 80 de Nginx sur le port 8088 de l'hôte
depends_on:
db:
condition: service_healthy # OBLIGATOIRE avec healthcheck

volumes:
db_data:

8. Configuration Nginx​

La configuration de Nginx doit pointer vers PHP-FPM en localhost car ils sont dans le même conteneur.

server {
listen 80;
index index.php;
server_name localhost;
root /var/www/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
# On communique avec PHP-FPM en local, dans le même conteneur
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
internal;
}
location ~ \.php$ {
return 404;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}

9. Lancement du projet​

  1. Créez votre fichier .env.local à partir des exemples ci-dessus.
  2. Démarrez les conteneurs (depuis PowerShell ou Git Bash) :
docker-compose up -d --build

Votre projet sera accessible sur http://localhost:8080.


10. Chargement des fixtures​

Si vous avez besoin de charger des fixtures de développement, vous pouvez le faire en exécutant la commande suivante :

docker-compose exec -e APP_ENV=dev app php bin/console doctrine:fixtures:load

11. Envoi de l'image sur Docker Hub​

Création et publication de l’image sur Docker Hub

Pour publier votre application Symfony en tant qu’image Docker "tout-en-un", suivez ces étapes :

  1. Construisez l’image :

Remplacez votre-nom-utilisateur/votre-nom-image-tout-en-un par votre nom d'image.

docker build -t votre-nom-utilisateur/votre-nom-image-tout-en-un:v1 .
  1. Publiez l’image :
docker login
docker push votre-nom-utilisateur/votre-nom-image-tout-en-un:v1

12. Utilisation de l’image publique​

Pour utiliser (en production) l’image publiée (par exemple sur Coolify) :

  • créez un fichier .env (ou renseignez les variables d'environnement directement dans Coolify)
  • utilisez ce docker-compose.yml simplifié.

Notez que le volume pour le code source n'est pas monté, car l'image est auto-suffisante.

services:
db:
image: mysql:8.0
container_name: ma_bdd_mysql
restart: unless-stopped
environment:
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s

app:
image: lbouquet/atedi2024-app:v1
container_name: mon_app_symfony
restart: unless-stopped
environment:
- DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@db:3306/${MYSQL_DATABASE}?serverVersion=8.0
- APP_ENV=${APP_ENV}
- LOAD_FIXTURES=${LOAD_FIXTURES} # Optionnel (true pour charger les fixtures)
ports:
- "8088:80" # On expose le port 80 de Nginx sur le port 8088 de l'hôte
depends_on:
db:
condition: service_healthy

volumes:
db_data:
astuce

La variable d'environnement LOAD_FIXTURES est optionnelle. Si vous la définissez à true, les fixtures seront chargées au démarrage du conteneur.

Puis lancez :

docker-compose up -d

13. Si erreur "exec format error"​

L'erreur exec /usr/local/bin/entrypoint.sh: exec format error est très probablement due à une incompatibilité d'architecture entre votre image (construite sur Windows/AMD64) et le serveur sur lequel elle est exécutée (souvent ARM64).

Solution - Construire une image multi-architecture avec Docker Buildx​

  1. Activez Buildx (si ce n'est pas déjà fait) :

    docker buildx install
    docker buildx create --use
  2. Modifiez votre commande de build pour créer une image multi-architecture :

    # Remplacez 'votre-nom-utilisateur/votre-nom-image-tout-en-un:v1' par votre nom d'image
    docker buildx build --platform linux/amd64,linux/arm64 -t votre-nom-utilisateur/votre-nom-image-tout-en-un:v1 --push .

Cette commande construira votre image pour les deux architectures (AMD64 et ARM64) et la publiera directement sur Docker Hub.


Bonnes pratiques​

  • Aucun secret commité : Les mots de passe sont dans .env.local (ignoré par Git).
  • Variables d'environnement : Chargées via env_file ou environment dans docker-compose.yml.
  • Image Docker propre : Aucune trace de secrets dans l'image publiée.
  • Versions spécifiques : L'utilisation de mysql:8.0 plutôt que latest garantit la reproductibilité des environnements.
  • Image unique : Simplifie le déploiement et la gestion en production.