FrankenPHP ♥️ Symfony
Integrating FrankenPHP in a fully dockerized Symfony application with Docker Compose in development and Kubernetes in production.
When thinking about how to run your PHP application on your server you have quite a few options nowadays. There are, for example, big players like Apache Webserver with the PHP module or Nginx with PHP-FPM. Both of them are battle-tested, but have one common drawback: Every request has to go through the whole application bootstrapping process before it can be answered. This is obviously something with a high potential for a performance boost.
Some projects like RoadRunner try to tackle this problem by booting the application only once, while others like Swool and ReactPHP try to do things asynchronously (plus keep certain things in memory).
Though the new kid on the block is FrankenPHP. It uses the existing web server Caddy and extends it with a module to run PHP. It is written in Go and it has a worker mode. It basically tries to bring all the good things together which gives us a lot of advantages.
Caddy is a secure and fast webserver. It’s is also extensible which made FrankenPHP possible. FrankenPHP uses the new SAPI, is compatible with existing PHP applications and offers a worker mode to keep the application in memory
KEVIN DUNGLAS (PRESENTATION FROM OCTOBER 14, 2022).
FrankenPHP is still in an early stage of development and not recommended to be used in production, but why not give it a try and see if it works?
Integration
So let’s integrate FrankenPHP in our current dockerized development setup and our Kubernetes cluster on AWS (Amazon Web Services) with our Symfony application.
Docker Image for FrankenPHP
First of all, we need to adjust our PHP docker images. It’s not a lot we need to do — just replace the base image you are using with the FrankenPHP one. The FrankenPHP image is currently using the official PHP 8.2 image as its base (tags were already requested). This means you can use all the other things you had configured and the custom settings you made in your .ini-file.
- from php:8.2-fpm-bullseye AS ...
+ from dunglas/frankenphp AS ...
Next, make sure a Caddyfile exists and is available inside the container.
COPY ./docker/files/etc/Caddyfile /etc/Caddyfile
Configure the server with a custom Caddyfile
Because of our setup we need to make two small adjustments to the default Caddyfile FrankenPHP provides.
As we usually deploy our projects to AWS, TLS termination happens in Cloudfront. Therefore we don’t need HTTPS support in Caddy and can disable it. We can replace the localhost part with :80.
{
# Debug
{$CADDY_DEBUG}
frankenphp {
worker /var/www/app/public/index.php
}
auto_https off
}
:80
log
route {
root * public/
# Add trailing slash for directory requests
@canonicalPath {
file {path}/index.php
not path */
}
redir @canonicalPath {path}/ 308
# If the requested file does not exist, try index files
@indexFiles file {
try_files {path} {path}/index.php index.php
split_path .php
}
rewrite @indexFiles {http.matchers.file.relative}
# FrankenPHP!
@phpFiles path *.php
php @phpFiles
encode zstd gzip
file_server
respond 404
}
Adjust Supervisor to run FrankenPHP
Next, let’s replace the currently configured php-fpm process in the supervisor configuration file with FrankenPHP, which just means to replace the command. Everything else can stay as it is. If you are not using Supervisor you probably need to make a very similar adjustment to your entrypoint script.
[program:frankenphp]
command = frankenphp run --config /etc/Caddyfile
…
Remove Nginx
In our case there were quite a few Nginx-related files in the project, but now we can get rid of all of them. With FrankenPHP in place we …
- don’t need Nginx sitting in front of php-fpm
- don’t need to build our custom Nginx image in the pipeline as we can serve the static files directly
- can get rid of a bunch of configuration files
- don’t need to deploy it to our Kubernetes cluster (one container less)
- don’t have our external dependency anymore between Nginx and php-fpm as it’s all in one container now
Install runtime for Symfony
Finally, install the appropriate runtime with composer …
composer require runtime/frankenphp-symfony
… and make sure it’s actually used by adding this to your composer.json. Alternatively, you can also do it with environment variables but why add new variables for things you are not going to change anyway?
"extra": {
"symfony": {
…
},
"runtime": {
"class": "Runtime\\FrankenPhpSymfony\\Runtime"
}
}
Done
That’s it. Now let’s give it a try!
If you had Xdebug configured before it should still be available and will show up in the toolbar at the bottom.
Conclusion
With these changes, we have our whole app in one container and this single container can handle requests without any additional server. Besides this, we also get a worker mode that gives us a considerable performance boost as well as access to new features like early hints.
It shouldn’t be used for production yet as it’s experimental but it’s definitely worth keeping an eye on it.