Tous les articles
Mer. 15 avril 2026 · 4 min de lecture

🐘 FrankenPHP : le runtime PHP qui change les règles du jeu

FrankenPHP

📚 Introduction

PHP-FPM a fait son temps. Pas qu’il soit mauvais — il a tenu la charge de l’écosystème PHP pendant plus de quinze ans — mais l’écart de performance avec les runtimes modernes (Node.js, Go, Bun, Rust) est devenu difficile à ignorer. La principale raison : chaque requête redémarre un cycle complet de bootstrap PHP, recharge le framework, recompile les opcodes (sauf OPcache), reconstruit la connexion à la base.

FrankenPHP propose une réponse pragmatique : un runtime moderne basé sur Caddy, capable d’exécuter PHP en mode “worker” (l’application reste en mémoire entre les requêtes), supporte HTTP/2 et HTTP/3 nativement, et peut produire un binaire unique qui embarque PHP + l’application + le serveur web.

⚡ Le worker mode, vraiment

Le worker mode est l’argument central de FrankenPHP. Au lieu de redémarrer le bootstrap PHP à chaque requête, on garde l’application en mémoire et on traite plusieurs requêtes par worker. Sur une application Laravel typique :

  • Le bootstrap Laravel (Service Providers, configuration, container) ne s’exécute qu’une seule fois par worker.
  • Les connexions Doctrine ou Eloquent restent ouvertes entre les requêtes.
  • Le cache OPcache n’a plus besoin d’être réchauffé à chaque démarrage.

Concrètement, sur les endpoints d’API que je gère, la latence moyenne passe de 25-30 ms (PHP-FPM) à 4-6 ms (FrankenPHP worker mode). Sur un endpoint très bavard en services injectés, on observe encore plus de gain.

🧱 Mise en place avec Laravel (via Octane)

Côté Laravel, le worker mode FrankenPHP passe par Laravel Octane, qui sert d’orchestrateur officiel pour les runtimes haute performance (Swoole, RoadRunner, et désormais FrankenPHP).

composer require laravel/octane
php artisan octane:install --server=frankenphp

L’installer télécharge le binaire FrankenPHP si nécessaire et publie la configuration Octane. On démarre ensuite l’application en worker mode avec :

php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=8000 --workers=auto

Côté Docker, le Dockerfile reste très simple :

FROM dunglas/frankenphp:latest-php8.3
 
WORKDIR /app
COPY . .
RUN composer install --no-dev --optimize-autoloader
 
CMD ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=8000"]

Plus de Nginx en frontal, plus de PHP-FPM séparé, plus de socket Unix entre les deux : un seul processus, un seul binaire à monitorer. Et toutes les commandes Octane habituelles (octane:reload, octane:status) fonctionnent telles quelles.

🎼 Mise en place avec Symfony (via le runtime FrankenPHP)

Côté Symfony, FrankenPHP s’utilise directement via son runtime officiel, sans passer par Octane.

composer require runtime/frankenphp-symfony

Le package expose un runtime compatible avec le composant symfony/runtime. On l’active simplement en pointant la variable d’environnement APP_RUNTIME :

APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime

Le Dockerfile typique est encore plus court qu’avec Laravel, parce qu’il n’y a pas de processus orchestrateur supplémentaire :

FROM dunglas/frankenphp:latest-php8.3
 
WORKDIR /app
COPY . .
RUN composer install --no-dev --optimize-autoloader
 
ENV APP_RUNTIME="Runtime\\FrankenPhpSymfony\\Runtime"
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]

Avec un Caddyfile minimal :

{
	frankenphp
}

:8080 {
	root * /app/public
	php_server
}

C’est FrankenPHP lui-même qui gère la boucle de worker : il charge le public/index.php, garde le kernel Symfony en mémoire et traite les requêtes successives sans reboot.

🪶 Le mode embed : un binaire pour les déployer tous

Le mode embed est probablement la fonctionnalité la plus surprenante. Il permet de produire un binaire statique qui contient :

  • le runtime PHP,
  • les extensions nécessaires,
  • l’application elle-même.
frankenphp build --output ./my-app
./my-app

Pour les outils internes, les CLI distribuées ou les démos qu’on veut faire tourner facilement sur n’importe quelle machine, c’est un changement radical. On obtient l’expérience “single binary” des applications Go, mais en PHP.

🔌 HTTP/3 et Server-Sent Events natifs

FrankenPHP s’appuyant sur Caddy, on hérite gratuitement de plusieurs capacités modernes :

  • HTTP/2 et HTTP/3 activés par défaut.
  • Certificats Let’s Encrypt automatiques.
  • Server-Sent Events et WebSocket sans configuration tierce.
  • Compression Zstd sur les réponses, en plus de Brotli et gzip.

Pour des fonctionnalités temps réel (notifications, dashboards live, IoT), c’est une stack qui devient compétitive avec Node.js sans changer de langage côté équipe.

⚠️ Les précautions à prendre

Le worker mode change le modèle mental de PHP. Quelques pièges à connaître :

  • Les variables globales et statiques persistent entre les requêtes. Code malpropre = fuites de données.
  • Les Service Providers ne sont plus rechargés à chaque requête : la moindre exception non gérée dans un singleton peut empoisonner les requêtes suivantes.
  • Le hot reload demande une commande explicite (frankenphp reload) pour les changements de code en production.
  • Toutes les extensions PHP ne sont pas thread-safe : vérifier la compatibilité avant de migrer (gd, imagick, intl sont OK ; certaines extensions exotiques le sont moins).

🎉 Conclusion

FrankenPHP est aujourd’hui l’option à considérer en premier pour une nouvelle application Laravel ou Symfony. Les gains de latence sont mesurables sans tuning particulier, le déploiement se simplifie, et l’écosystème suit (les fournisseurs comme Coolify et même certains PaaS l’intègrent en natif).

Sur les projets existants en PHP-FPM, la migration est progressive : on peut commencer par le mode classique sans worker, puis activer le worker mode une fois le code audité. La courbe est douce, et les bénéfices arrivent vite.

🔗 Liens utiles