Onze sur douze

Le composer.json de chaque service avait ça dans sa section post-install-cmd : "post-install-cmd": [ "bin/console cache:clear --env=prod", "bin/console doctrine:migrations:migrate --no-interaction" ] post-install-cmd s’exécute pendant composer install, qui dans le Dockerfile de production tourne au moment du build de l’image. Il n’y a pas de base de données disponible pendant un build Docker. La commande de migration échouait silencieusement, se connectait à rien, ou était ignorée par Doctrine faute de schéma à comparer. Dans tous les cas, elle ne migrait rien. ...

17 mai 2026 · 6 min · Guillaume Delré

Démarré ne veut pas dire prêt

Le rolling deploy avait l’air propre. Un nouveau pod démarrait. Kubernetes voyait le healthcheck passer — php -v renvoyait zéro — et commençait à router du trafic vers le nouveau container. Pendant les quarante secondes suivantes — sur les soixante possibles — ce container était en train de poller la base de données. Les requêtes qui atterrissaient dessus pendant cette fenêtre récoltaient des erreurs. Pas beaucoup — la fenêtre était courte — mais assez pour apparaître comme du bruit dans le monitoring. Le genre de bruit qu’on classe comme « problème réseau transitoire » et qu’on ne signale nulle part. Le déploiement a réussi. Le pod a fini par devenir prêt. Le mécanisme qui en était la cause était toujours là, attendant le prochain déploiement. ...

17 mai 2026 · 9 min · Guillaume Delré

Le cache qui nous mentait

La première fois qu’on a lancé deux replicas du même service Symfony derrière un load balancer, tout avait l’air d’aller. Les health checks passaient. Le trafic se répartissait proprement. Les temps de réponse étaient bons. Puis quelqu’un a remarqué que le rate limiter se comportait bizarrement. Cinq appels à l’API, accès bloqué. Cinq appels supplémentaires à la requête suivante, accès accordé. Selon quel pod répondait, on était une personne différente. ...

16 mai 2026 · 8 min · Guillaume Delré

Quinze minutes avant le premier test

Le pipeline avait deux stages qui n’avaient rien à voir avec le code : provision et deprovision. Entre eux, dans l’ordre : phpunit, phpmetrics, behat. stages: - build - provision - phpunit - phpmetrics - behat - deprovision - deploy Avant que la première assertion s’exécute, quinze minutes s’étaient écoulées. Terraform avait cloné un dépôt d’infrastructure, s’était authentifié sur Azure, avait appliqué une configuration de VM. Ansible s’était connecté à la nouvelle VM, avait installé PHP, configuré l’application, câblé une base de données et une instance Redis. Ensuite les tests tournaient. Ensuite Terraform détruisait ce qu’Ansible avait construit. ...

16 mai 2026 · 5 min · Guillaume Delré

L'hôte qui cachait le graphe

Chaque service de la plateforme avait ces six variables : APP__GATEWAY__PRIVATE__HOST="platform.internal" APP__GATEWAY__PRIVATE__PORT=80 APP__GATEWAY__PRIVATE__SCHEME="http" APP__GATEWAY__PUBLIC__HOST="platform.internal" APP__GATEWAY__PUBLIC__PORT=80 APP__GATEWAY__PUBLIC__SCHEME="http" Treize services, six variables chacun, une seule valeur. En lisant la config d’un service quelconque, l’architecture semblait plate. Tout parlait au même hôte. C’était tout le tableau. Ce ne l’était pas. Comment fonctionnait la gateway La gateway se trouvait devant chaque service et gérait tout le trafic inter-services. Un service appelant l’API content construisait une requête vers http://platform.internal/content/api/ — la gateway la recevait, identifiait la cible depuis le chemin de l’URL, et la transmettait au bon backend. Chaque client HTTP inter-service dans framework.yaml suivait le même schéma : ...

15 mai 2026 · 5 min · Guillaume Delré

Aucun témoin

Le service s’était crashé. On avait l’alerte. On avait le timestamp à la seconde. On avait Loki ouvert avec une requête prête. Ce qu’on n’avait pas, c’était les logs des cinq minutes précédant le crash. Promtail tournait. Il était healthy. Il collectait les logs de tous les autres services sans problème. Mais pour celui-ci, dans la fenêtre qui comptait, il n’y avait rien. Le service s’était crashé sans laisser de trace. ...

15 mai 2026 · 8 min · Guillaume Delré

Ce qui survit au build

À un moment de l’audit de migration cloud, quelqu’un a lancé ça : docker run --rm <image> php -r "var_dump(require '.env.local.php');" La sortie montrait tout ce que composer dump-env prod avait compilé dans l’image au moment du build. Ce qui voulait dire tout ce qui se trouvait dans le fichier .env quand l’image avait été construite. Ce qui voulait dire, entre autres, ça : INFLUXDB_INIT_ADMIN_TOKEN=<influxdb-admin-token> GF_SECURITY_ADMIN_USER=admin GF_SECURITY_ADMIN_PASSWORD=admin123 BLACKFIRE_CLIENT_ID=<blackfire-client-id> BLACKFIRE_CLIENT_TOKEN=<blackfire-client-token> BLACKFIRE_SERVER_ID=<blackfire-server-id> BLACKFIRE_SERVER_TOKEN=<blackfire-server-token> NGROK_AUTHTOKEN=replace-me-optionnal Vingt-cinq variables au total. Chaque credential accumulé dans le .env racine sur trois ans, désormais permanent dans un layer d’image. ...

14 mai 2026 · 6 min · Guillaume Delré

Le fantôme du runner CI

APP__COLD_STORAGE__FILESYSTEM_PATH="/home/jenkins-slave/share_media/media" APP__COLD_STORAGE__FILESYSTEM_PATH_CACHE="/home/jenkins-slave/share_media/media/cache" APP__COLD_STORAGE__RAW_IMAGE_PATH="/home/jenkins-slave/share_media/media_raw" APP__SHARE_STORAGE__FILESYSTEM_PATH="/home/jenkins-slave/share_storage" Ces lignes se trouvaient dans le .env de production du service media. Pas le staging. Pas un override local. La production, committée dans le dépôt, lue à chaque démarrage. Les chemins se terminent là où on s’y attendrait : /media, /share_storage. Ils commencent ailleurs : /home/jenkins-slave, le répertoire home d’un runner CI issu d’une ancienne installation Jenkins. Comment le home d’un runner atterrit dans la config de production La plateforme avait grandi depuis une seule machine. Un serveur faisait tout tourner — l’application, le runner CI, la base de données, le stockage de fichiers. Les fichiers transitaient entre l’app et le système CI via NFS : un répertoire monté sur le même hôte, accessible aux containers comme au runner. ...

14 mai 2026 · 7 min · Guillaume Delré

Construire un homelab self-hosted avec Docker Compose et Traefik

Ça fait des années que j’avais envie d’un homelab à la maison. Un endroit à moi pour héberger mes outils de développement, surveiller mes machines, faire tourner de la domotique, tester des trucs sans risquer de casser quoi que ce soit d’important. L’idée est simple. La mise en place un peu moins. À l’époque, Kubernetes n’existait pas encore. Les options pour faire tourner plusieurs services sur une machine se résumaient à du scripting bash, des configurations Nginx écrites à la main, et beaucoup de café. Les tutoriels “homelab pour les humains” brillaient par leur absence. ...

17 février 2026 · 12 min · Guillaume Delré

Symfony 8.0 : PHP 8.4 minimum, objets paresseux natifs et FormFlow

Symfony 8.0 est sorti le 27 novembre 2025, le même jour que 7.4. Il exige PHP 8.4 et abandonne tout ce qui était déprécié dans 7.4. Les deux changements les plus intéressants sont ce qu’il arrête de faire et ce qu’il commence à faire avec PHP 8.4. Les objets paresseux natifs Le système de proxy de Symfony, utilisé pour l’initialisation paresseuse des services et les proxies d’entités de Doctrine, a historiquement reposé sur la génération de code. Les classes proxy étaient générées au cache warmup, stockées sous forme de fichiers, et chargées à la demande. Ça fonctionnait, mais ça ajoutait une vraie complexité : des fichiers générés à gérer, un cache à invalider, du code qui ne ressemblait en rien à la classe qu’il proxyifiait. ...

12 janvier 2026 · 7 min · Guillaume Delré