Technique · Mis à jour le
Comment lancer des tâches planifiées (crons) quand votre application Symfony tourne dans Docker ? Il existe plusieurs façons de gérer des crons avec Docker. Voici celle que nous utilisons : lancer deux fois la même image, avec deux rôles différents.
Le principe
La méthode la plus courante consiste à utiliser la crontab du serveur hôte. Elle déclenche alors des commandes à l’intérieur du container. Ça fonctionne, mais la planification vit en dehors de votre projet.
Nous préférons une autre approche :
- on construit une seule image, qui contient l’application et le programme cron ;
- on la lance deux fois : un container sert le site web, l’autre exécute uniquement les crons.
Ainsi, la planification est versionnée avec le code, et les deux containers ont exactement le même environnement.
Étape 1 : écrire le fichier cron
On crée un fichier crontab à la racine du projet, avec les commandes à lancer :
* * * * * cd /app && /usr/local/bin/php bin/console app:ma-commande > /proc/1/fd/1 2>&1
Deux détails importants :
- La redirection vers
/proc/1/fd/1envoie la sortie de la commande dans les logs du container. Vous la retrouvez avecdocker compose logs. Une redirection vers/dev/null, elle, ferait disparaître toutes les erreurs. - Le fichier doit se terminer par une ligne vide, sinon cron ignore la dernière ligne.
Étape 2 : installer cron dans l’image
Dans le Dockerfile, on installe le démon cron et on enregistre notre fichier :
RUN apt-get update \
&& apt-get install -y --no-install-recommends cron \
&& rm -rf /var/lib/apt/lists/*
COPY crontab /etc/app-crontab
RUN chmod 0644 /etc/app-crontab && crontab /etc/app-crontab
On obtient une image Symfony avec tout l’environnement d’exécution (PHP, serveur web), plus un cron prêt à démarrer.
Étape 3 : déclarer deux services dans Docker Compose
Dans le fichier compose.yaml, on déclare deux services basés sur la même image : php pour le HTTP, crond pour les tâches planifiées.
volumes:
shared-tmpfs:
driver: local
driver_opts:
type: "tmpfs"
device: "tmpfs"
o: "size=256m"
services:
php:
image: XXX
pull_policy: always
restart: always
ports:
- target: 80
published: 80
protocol: tcp
environment:
SERVER_NAME: :80
volumes:
- shared-tmpfs:/app/var
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
crond:
image: XXX
pull_policy: always
restart: always
command: cron -f
healthcheck:
test: ['CMD','true']
volumes:
- shared-tmpfs:/app/var
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
Ce qu’il faut regarder :
command: cron -f: le servicecrondremplace la commande de démarrage par cron, lancé au premier plan. Le servicephp, lui, garde son point d’entrée habituel.- Le
healthcheckneutralisé : l’image vérifie normalement que le serveur web répond. Le container cron n’a pas de serveur web, on désactive donc ce contrôle. - Les volumes
timezoneetlocaltime: ils alignent le fuseau horaire sur celui du serveur. Sans eux, vos crons tournent à l’heure UTC.
Partager des données entre les deux containers
Notre application utilise une base SQLite comme cache un peu évolué. Les deux containers doivent donc voir la même base.
Nous avons choisi un système de fichiers en mémoire vive (tmpfs), partagé entre les deux services via le volume shared-tmpfs. La base est créée automatiquement au démarrage du container php.
Attention aux variables d’environnement
C’est le piège classique. Cron lance les tâches avec un environnement quasi vide. Les variables définies dans compose.yaml (comme DATABASE_URL) ne sont donc pas transmises à vos commandes.
Si votre configuration est dans les fichiers .env de Symfony embarqués dans l’image, pas de souci : Symfony les lit lui-même. Mais si elle passe par Docker, deux solutions :
- exporter les variables au démarrage du container, par exemple avec
printenv > /etc/environmentavant de lancer cron ; - remplacer cron par Supercronic, un cron pensé pour les containers. Il transmet les variables d’environnement, écrit ses logs sur la sortie standard et lit directement un fichier crontab.
Une alternative 100 % Symfony : le composant Scheduler
Depuis Symfony 6.3, le composant Scheduler permet de déclarer les tâches planifiées directement dans le code PHP. Plus besoin de cron : le second container lance simplement un worker Messenger.
scheduler:
image: XXX
restart: always
command: php bin/console messenger:consume scheduler_default
C’est une bonne option si vous utilisez déjà Messenger. La planification reste alors au même endroit que votre code métier.
À lire aussi