Aller au contenu principal

Symfony avec Docker et MySQL (Image Tout-en-un)

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

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 deux 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).

1. Structure des fichiers

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)

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 ###

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 ###

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. Si vous avez besoin de charger des fixtures de développement

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. 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 (en production)

Pour utiliser 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.