FrankenPHP and Laravel Octane with Docker
Extracto
Just random stuff.
Contenido
FrankenPHP is a project I've been keeping an eye on for a while. It's an alternative way to run PHP applications on the web without using php-fpm, which makes it easier to deploy with Docker as you don't need to deploy multiple containers for both nginx and php-fpm, or deploy one container that runs both processes. It boasts a bunch of cool features, but the one I'm most interested in is worker mode. Worker mode does a similar job to RoadRunner or Swoole, where it keeps your application booted in memory and re-uses the same instance to serve multiple HTTP requests. Unlike php-fpm that tears down the world and builds it fresh for each request, this cuts out a lot of execution time especially if you're using frameworks like Symfony or Laravel that run a lot of bootstrapping code before getting to your actual application code. The only thing holding me back from using it so far was that Laravel Octane didn't have out-of-the-box support for FrankenPHP. That was until yesterday when The Laravel team announced the long-lived FrankenPHP PR to Octane finally got merged!
All of my projects are dockerized, and I wanted to show you how you can use FrankenPHP alongside Laravel Octane in Docker.
Update Laravel Octane
First thing's first, you need to update Octane to 2.2.3 to get the new FrankenPHP support. There was also
a bug in earlier versions of 2.2.x which prevented Octane being used in
a Docker container, so we want to at least go to 2.2.3.
1composer update laravel/octane:^2.2.3
Also re-run the Octane install command which will add the frankenphp-worker.php script to your public directory.
This will be the "entrypoint" of your application and replaces public/index.php.
1php artisan octane:install
Octane will prompt you and want to download the FrankenPHP binary, you can say no because we'll be running FrankenPHP in Docker and don't need the binary in our project 🙂
Create a Dockerfile for the web server
Next, create the following Dockerfile.
1FROM dunglas/frankenphp
2
3RUN install-php-extensions pcntl
4
5COPY . /app
6
7ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
Nothing special here. We're using the dunglas/frankenphp image, installing the pcntl extension, copying our
application source code into the container, and then starting the FrankenPHP web server via the octane:frankenphp
artisan command.
The pcntl extension is required because Octane listens for SIGINT and SIGTERM signals. If you need other PHP
extensions, add them to the list.
We're also starting FrankenPHP using octane:frankenphp instead of octane:start simply because I don't want to
have to rely on OCTANE_SERVER=frankenphp being in my .env file, and I like it to be explicit that it's using
FrankenPHP. If you inspect the source code of Octane, the octane:start command just calls octane:frankenphp if
your application is configured to use FrankenPHP.
Update docker-compose.yml
Finally, in your docker-compose.yml file, use the new image:
1services:
2 web:
3 build:
4 context: .
5 dockerfile: infrastructure/web/Dockerfile
6 entrypoint: php artisan octane:frankenphp --max-requests=1
7 ports:
8 - "80:8000"
9 volumes:
10 - .:/app
This file exists at the root of my project directory, so context: . will ensure that the COPY in the Dockerfile
copies the correct files. My Dockerfile exists at infrastructure/web/Dockerfile, so we also specify that path. Modify
this if your Dockerfile exists elsewhere.
We override the entrypoint to add --max-requests=1. This is simply so that code changes take immediate effect.
Octane does have a --watch flag to automatically reload the web server on changes
to application code, but it requires installing Node inside the Docker image and using the chokidar npm package. I
don't want my image to have Node, or install a package just to watch for files changing, so I just reload the server
after every request instead. Note that this does negate the benefit of worker mode as your application won't remain
booted between requests anymore, but only in your local development environment. When you deploy this image to production
the --max-requests argument won't be present and you'll get the full speed benefits of worker mode.
By default Octane runs on port 8000, and I bind that to port 80 on my local machine just so I can access my app
at http://localhost without specifying a port.
Well that was easy
Start up your container and go hit localhost in your browser. You should see your application!

If we use phpinfo(), we should also see that FrankenPHP is our web server.
