Blog / Article #135
Docker + PHP + Cron, le combo

star

15 Septembre 2026
Illustration rétro-futuriste d'un container Docker PHP avec un cron automatisé

Le cron. Ce vieux compagnon des admins sys qui, malgré son âge vénérable, reste l'un des moyens les plus simples et les plus fiables de lancer des tâches planifiées. Sauf que dès qu'on parle de containers Docker, certains s'empressent de sortir des solutions beaucoup plus lourdes : Kubernetes CronJobs, orchestrateurs divers ou services SaaS payants.

Pourtant, lorsqu'on veut simplement exécuter périodiquement une commande PHP, un script ou une commande Symfony, il est parfaitement possible de rester simple, léger et fiable. Pas besoin de transformer trois lignes de crontab en infrastructure distribuée.

Pourquoi ne pas simplement lancer cron dans le container PHP ?

Techniquement, rien ne l'interdit. Le problème vient surtout du fonctionnement de Docker : un container reste actif tant que son processus principal, le fameux PID 1, continue de tourner.

Un cron lancé de manière classique se détache et passe en arrière-plan. Si le processus principal du container se termine, Docker considère alors que le container a terminé son travail et l'arrête.

La solution est très simple : lancer cron en foreground.

cron -f

Le processus cron devient alors le processus principal du container et Docker peut correctement surveiller son état. Pas besoin de supervisord, de systemd ou d'un autre gestionnaire de processus uniquement pour faire tourner cron.

Attention cependant à un comportement important : si le container est arrêté au moment où une tâche devait être exécutée, cette exécution est perdue. Lorsque le container redémarre, cron reprend normalement son fonctionnement, mais il ne rattrape pas automatiquement les tâches manquées pendant l'arrêt.

Un container dédié au cron

Dans une application Docker comportant déjà plusieurs services, une approche propre consiste à avoir un container dédié aux tâches planifiées. Il peut utiliser la même image PHP que l'application, mais avec cron comme processus principal.

On évite ainsi de faire tourner PHP-FPM, cron et éventuellement d'autres processus dans le même container.

Voici un exemple minimal basé sur l'image PHP officielle :

FROM php:8.4-cli

RUN apt-get update \
    && apt-get install -y --no-install-recommends cron \
    && rm -rf /var/lib/apt/lists/*

COPY script.php /usr/local/bin/script.php

RUN chmod 0755 /usr/local/bin/script.php

RUN echo '* * * * * root /usr/local/bin/php /usr/local/bin/script.php >> /proc/1/fd/1 2>> /proc/1/fd/2' \
    > /etc/cron.d/php-cron \
    && chmod 0644 /etc/cron.d/php-cron

CMD ["cron", "-f"]

La tâche est ici exécutée toutes les minutes.

Un petit script PHP pour tester

Pour vérifier simplement que tout fonctionne :

<?php

echo date('Y-m-d H:i:s') . " - Script exécuté\n";

On peut ensuite construire l'image :

docker build -t php-cron .

Puis démarrer le container :

docker run -d --name php-cron php-cron

Et suivre directement les exécutions :

docker logs -f php-cron

Une nouvelle ligne doit apparaître toutes les minutes.

Pourquoi envoyer les logs vers stdout et stderr ?

Dans un container Docker, il est généralement préférable d'éviter d'écrire les logs applicatifs directement dans des fichiers comme /var/log/cron.log.

Docker sait déjà récupérer les sorties standard et les erreurs du processus principal. On peut donc rediriger la sortie de la tâche cron vers les descripteurs du PID 1 :

>> /proc/1/fd/1 2>> /proc/1/fd/2

La sortie standard arrive dans stdout et les erreurs dans stderr. Elles deviennent alors directement accessibles avec :

docker logs php-cron

Cette méthode fonctionne également beaucoup mieux avec les systèmes centralisés de collecte de logs utilisés sur les serveurs Docker.

Les principaux pièges à éviter

1. Les chemins relatifs

Cron possède un environnement beaucoup plus minimaliste qu'un shell interactif. Évitez donc de compter sur le répertoire courant ou sur votre PATH.

Préférez :

/usr/local/bin/php /app/bin/console app:ma-commande

plutôt que :

php ./bin/console app:ma-commande

2. Les variables d'environnement

C'est probablement le piège le plus important avec cron dans Docker. Cron ne transmet pas forcément aux commandes exactement le même environnement que celui disponible dans le container.

Une application qui dépend fortement de variables comme DATABASE_URL, APP_ENV, des tokens d'API ou d'autres variables injectées par Docker peut donc fonctionner parfaitement lorsqu'une commande est lancée manuellement, puis échouer lorsqu'elle est exécutée par cron.

Il faut donc vérifier explicitement l'environnement disponible pour les tâches cron et ne pas supposer qu'il sera identique à celui d'un shell ouvert avec docker exec.

3. Les permissions

Dans l'exemple précédent, la tâche est exécutée par root parce que le format des fichiers placés dans /etc/cron.d/ permet de préciser l'utilisateur :

* * * * * root commande

Pour une application réelle, il peut être préférable d'exécuter la commande avec le même utilisateur que l'application, par exemple www-data, à condition que celui-ci possède les permissions nécessaires.

4. Le foreground

Le point essentiel reste :

CMD ["cron", "-f"]

Sans -f, cron se détache et passe en arrière-plan. Le processus lancé par Docker se termine alors et le container s'arrête.

5. Les extensions et dépendances PHP

Le container cron doit disposer des mêmes dépendances que les commandes qu'il doit exécuter.

Si votre application utilise PDO MySQL, Redis, Intl, Composer ou d'autres extensions, elles doivent évidemment être présentes dans l'image utilisée par le container cron.

C'est justement pour cette raison qu'il est souvent intéressant d'utiliser la même image PHP que celle de l'application.

Et avec Docker Compose ?

Dans une vraie application, on peut très facilement séparer le service PHP principal du service cron tout en utilisant la même image.

Par exemple :

services:

  php:
    image: mon-application:latest
    restart: unless-stopped

  cron:
    image: mon-application:latest
    restart: unless-stopped
    command: ["cron", "-f"]

L'image contient PHP, l'application et cron, mais chaque container conserve un rôle précis : le premier exécute l'application, le second s'occupe uniquement des tâches planifiées.

Cela évite d'utiliser supervisord uniquement pour maintenir plusieurs processus dans le même container.

Une autre solution : laisser cron sur l'hôte

Il existe également une approche encore plus simple dans certains cas : ne pas installer cron dans Docker du tout.

Le cron du serveur hôte peut directement déclencher une commande dans un container existant :

* * * * * cd /opt/mon-app && docker compose exec -T php php bin/console app:ma-commande

Ou créer un container temporaire uniquement pour exécuter la tâche :

* * * * * cd /opt/mon-app && docker compose run --rm php php bin/console app:ma-commande

Les deux approches ont leurs avantages.

docker compose exec utilise un container PHP déjà en fonctionnement. C'est rapide et ne crée pas un nouveau container à chaque exécution.

docker compose run --rm crée au contraire un container temporaire pour la tâche puis le supprime lorsqu'elle est terminée. L'exécution est mieux isolée, au prix d'un léger coût de démarrage supplémentaire.

Pour quelques tâches sur un serveur unique, utiliser le cron de l'hôte est donc une solution parfaitement valable et souvent extrêmement simple à maintenir.

Quand cette solution ne suffit plus

Un simple cron est parfaitement adapté lorsque l'on possède un serveur Docker ou quelques machines et que l'on souhaite lancer périodiquement des commandes.

En revanche, les choses deviennent différentes lorsque l'infrastructure doit gérer plusieurs nœuds, de la haute disponibilité, des tâches distribuées, des reprises automatiques, des dépendances entre jobs ou une supervision avancée.

Dans ce cas, des solutions comme les Kubernetes CronJobs ou des ordonnanceurs spécialisés commencent à avoir un véritable intérêt.

Mais il n'est pas nécessaire de résoudre aujourd'hui les problèmes d'une infrastructure que vous n'aurez peut-être jamais.

Conclusion : rester simple tant que c'est possible

Faire tourner des tâches cron avec Docker n'a rien de particulièrement compliqué.

Pour un container dédié, cron -f suffit généralement. Les logs peuvent être envoyés directement vers stdout et stderr afin de profiter de docker logs, et le container peut utiliser exactement la même image PHP que l'application.

Pour une infrastructure encore plus simple, le cron du serveur hôte peut également déclencher directement les commandes nécessaires avec docker compose exec ou docker compose run.

Le plus important est finalement de choisir la solution adaptée au problème réel. Si vous avez trois tâches Symfony à exécuter toutes les nuits sur un serveur Docker, vous n'avez probablement pas besoin d'un orchestrateur distribué, d'un dashboard avec 40 graphiques et d'un abonnement SaaS à 99 dollars par mois.

Un cron, un container et quelques lignes de configuration peuvent parfaitement faire le travail. Et si les besoins évoluent un jour, il sera toujours temps de sortir l'artillerie lourde.

Publié le 15/09/2026 · 7 min de lecture Partager X LinkedIn

← Précédent Api error: connection lost mid-response – ou l’art de se faire ghoster par claude