-
-
Notifications
You must be signed in to change notification settings - Fork 161
Release 4.0: Add FrankenPHP & more Laravel optmizations #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
This would be super cool to have! Great work on this so far! |
# Conflicts: # .github/workflows/service_docker-build-and-publish.yml
|
This would really be a nice addition.. |
|
This will be a wonderful addition! |
|
This would be a great feature! 🚀 |
|
@jaydrogers - while FrankenPHP is really fantastic. I do find the json logs from Caddy that Franken is built upon to be cumbersome to look at when debugging. Fine if you were to ingest them into elasticache but a headache to view as a user developing software locally. Could you consider compiling in more logging options or do something like this? Another reason we could do with a franken builder option. But maybe you could build that in by default and allow configuring it. |
| --with github.com/dunglas/frankenphp=./ \ | ||
| --with github.com/dunglas/frankenphp/caddy=./caddy/ \ | ||
| --with github.com/dunglas/caddy-cbrotli \ | ||
| # Mercure and Vulcain are included in the official build, but feel free to remove them |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--with github.com/caddyserver/transform-encoder \
Would you consider installing this by default to give people the option of an alternative logging format rather than the caddy default which is not really designed for humans.
See: https://caddyserver.com/docs/modules/caddy.logging.encoders.transform
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have CADDY_LOG_FORMAT set to console by default and LOG_LEVEL_OUTPUT set to warn by default.
If you change LOG_LEVEL_OUTPUT to info, then you'll start seeing logs like this:
spin-production_php.1.wg4clerezt9c@spin-test-deploy | 2025/10/08 19:04:11.570 INFO http.log.access.log0 handled request {"request": {"remote_ip": "10.0.1.10", "remote_port": "39440", "client_ip": "10.0.1.10", "proto": "HTTP/2.0", "method": "GET", "host": "10.0.1.17:8443", "uri": "/up", "headers": {"Accept-Encoding": ["gzip"], "User-Agent": ["Go-http-client/2.0"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": ""}}, "bytes_read": 0, "user_id": "", "duration": 0.546836366, "size": 858, "status": 200, "resp_headers": {"X-Content-Type-Options": ["nosniff"], "X-Frame-Options": ["SAMEORIGIN"], "Date": ["Wed, 08 Oct 2025 19:04:11 GMT"], "Vary": ["Accept-Encoding"], "Strict-Transport-Security": ["max-age=31536000; includeSubDomains"], "Alt-Svc": ["h3=\":8443\"; ma=2592000"], "Cache-Control": ["no-cache, private"], "Content-Type": ["text/html; charset=UTF-8"], "Content-Encoding": ["gzip"], "Referrer-Policy": ["strict-origin-when-cross-origin"]}}
spin-production_php.1.wg4clerezt9c@spin-test-deploy | 2025/10/08 19:04:16.927 INFO http.log.access.log0 handled request {"request": {"remote_ip": "10.0.1.10", "remote_port": "39440", "client_ip": "1.2.3.4", "proto": "HTTP/2.0", "method": "GET", "host": "spin-deploy.example.com", "uri": "/info.php", "headers": {"X-Forwarded-Server": ["2faca4915f81"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-User": ["?1"], "X-Forwarded-Port": ["443"], "Priority": ["u=0, i"], "X-Forwarded-For": ["1.2.3.4"], "Sec-Fetch-Dest": ["document"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"], "Upgrade-Insecure-Requests": ["1"], "Pragma": ["no-cache"], "Accept-Language": ["en-US,en;q=0.5"], "X-Forwarded-Host": ["spin-deploy.example.com"], "Accept-Encoding": ["gzip, deflate, br, zstd"], "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0"], "Sec-Fetch-Site": ["none"], "Te": ["trailers"], "Cookie": ["REDACTED"], "X-Forwarded-Proto": ["https"], "Cache-Control": ["no-cache"], "X-Real-Ip": ["1.2.3.4"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": ""}}, "bytes_read": 0, "user_id": "", "duration": 0.016502452, "size": 26430, "status": 200, "resp_headers": {"Content-Type": ["text/html; charset=UTF-8"], "Content-Encoding": ["zstd"], "Referrer-Policy": ["strict-origin-when-cross-origin"], "X-Content-Type-Options": ["nosniff"], "X-Frame-Options": ["SAMEORIGIN"], "Alt-Svc": ["h3=\":8443\"; ma=2592000"], "Vary": ["Accept-Encoding"], "Strict-Transport-Security": ["max-age=31536000; includeSubDomains"]}}
spin-production_php.1.wg4clerezt9c@spin-test-deploy | 2025/10/08 19:04:18.877 INFO http.log.access.log0 handled request {"request": {"remote_ip": "10.0.1.10", "remote_port": "39440", "client_ip": "1.2.3.4", "proto": "HTTP/2.0", "method": "GET", "host": "spin-deploy.example.com", "uri": "/info.php", "headers": {"X-Forwarded-Port": ["443"], "X-Forwarded-Proto": ["https"], "Cache-Control": ["no-cache"], "Cookie": ["REDACTED"], "X-Forwarded-Server": ["2faca4915f81"], "Accept-Language": ["en-US,en;q=0.5"], "Priority": ["u=0, i"], "X-Forwarded-For": ["1.2.3.4"], "Upgrade-Insecure-Requests": ["1"], "Sec-Fetch-Dest": ["document"], "Sec-Fetch-Site": ["none"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"], "Sec-Fetch-Mode": ["navigate"], "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0"], "X-Real-Ip": ["1.2.3.4"], "Pragma": ["no-cache"], "Te": ["trailers"], "X-Forwarded-Host": ["spin-deploy.example.com"], "Accept-Encoding": ["gzip, deflate, br, zstd"], "Sec-Fetch-User": ["?1"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": ""}}, "bytes_read": 0, "user_id": "", "duration": 0.007419604, "size": 26423, "status": 200, "resp_headers": {"X-Frame-Options": ["SAMEORIGIN"], "Alt-Svc": ["h3=\":8443\"; ma=2592000"], "Content-Encoding": ["zstd"], "Referrer-Policy": ["strict-origin-when-cross-origin"], "X-Content-Type-Options": ["nosniff"], "Content-Type": ["text/html; charset=UTF-8"], "Vary": ["Accept-Encoding"], "Strict-Transport-Security": ["max-age=31536000; includeSubDomains"]}}
spin-production_php.1.wg4clerezt9c@spin-test-deploy | 2025/10/08 19:04:41.039 INFO http.log.access.log0 handled request {"request": {"remote_ip": "10.0.1.10", "remote_port": "39440", "client_ip": "10.0.1.10", "proto": "HTTP/2.0", "method": "GET", "host": "10.0.1.17:8443", "uri": "/up", "headers": {"Accept-Encoding": ["gzip"], "User-Agent": ["Go-http-client/2.0"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": ""}}, "bytes_read": 0, "user_id": "", "duration": 0.035062683, "size": 857, "status": 200, "resp_headers": {"Strict-Transport-Security": ["max-age=31536000; includeSubDomains"], "X-Content-Type-Options": ["nosniff"], "X-Frame-Options": ["SAMEORIGIN"], "Alt-Svc": ["h3=\":8443\"; ma=2592000"], "Date": ["Wed, 08 Oct 2025 19:04:41 GMT"], "Content-Type": ["text/html; charset=UTF-8"], "Referrer-Policy": ["strict-origin-when-cross-origin"], "Cache-Control": ["no-cache, private"], "Content-Encoding": ["gzip"], "Vary": ["Accept-Encoding"]}}
I know that's probably not ideal (but slightly better than defaults?).
Allowing customization of the logs
I really like your suggestion to have this possible, I am just hesitant to merge something that I don't know much about (this FrankenPHP image is my first time running FrankenPHP and Caddy, so I don't want to bite off more than I can chew 🤪)
Would you mind opening your proposal up as a feature request? I'd like to get more discussion on it from the community if there are any native ways we can do this without including too many plugins #66
…roved error handling. Added support for multiple databases, migration modes, and seeding. Updated documentation to reflect these changes and added a new script for testing database connections.
|
Just keeping everyone on this thread updated with some major changes that I just pushed. I added this to the top comment at the top, but wanted to email it out as well. My recent changes include a major refactor of our "Laravel Automations" script. 🤩 New FeaturesLaravel Automations Script ImprovementsThe Laravel Automations script has been completely refactored to make it easier to support advanced Laravel features. Tons of new features are now available: "php artisan optmize" now run by defaultInstead of setting If you don't want to use Added support for "migration modes"We now support different migration modes of
Specify which database connections to run migrations withIf you run multiple databases with a multi-tenant Laravel application, you may need to specify your exact database connection that you'd like to use. We created Added "--seed" option to migrationsLaravel has a helpful flag of Easier debuggingIf you're running into issues with automations, set |
…php variation - Introduced a HEALTHCHECK command in the Dockerfile with specified parameters. - Updated Caddyfile to define healthcheck endpoints and log skipping for both default and custom healthcheck paths. - Enhanced full Caddyfile to redirect localhost healthcheck requests to HTTPS.
Update for those who didn't get yesterday's update 😃We're in beta 🥳 |
* Fix missing gettext, procps and zip on FrankenPHP Variation * Remove gettext (we don't need envsubst for FrankenPHP) * Update default configurations to include libstdc++6 and clarify procps requirements for Debian images * Removed zip -- we don't need it --------- Co-authored-by: Jay Rogers <jaydrogers@users.noreply.serversideup.net>
…the new hash in contributing documentation
…HP variation' in the getting started guide.
- Introduced a new section explaining what Docker is and its advantages for containerizing applications. - Added a demo video link for the Spin project to help users get started with Docker. - Updated the FPM-NGINX section to reflect its historical significance and current relevance. - Marked the NGINX Unit variation as deprecated due to its discontinuation. - Added a new section for the FrankenPHP variation, highlighting its features and benefits.
| The `fpm-nginx` variation is great for people who want to run Laravel applications or similar. This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two processes required to run this variation. We use S6 Overlay to ensure the container health is accurate. | ||
| Over the last 15+ years `fpm-nginx` has been "the way" to run PHP web applications, including Laravel. Since around 2010, it's been adopted and trusted by some of the best PHP system administrators out there. As of 2025, we're starting to see other options like FrankenPHP push the industry forward, but `fpm-nginx` is very stable and still widely used today. | ||
|
|
||
| The only caveat with running FPM + NGINX together with Docker is it requires two processes (which can lead to strange container behaviors). Thankfully, we took this all in mind with our design by using 's been trusted and deployed millions of times and there's a large adoption This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two processes required to run this variation. We use S6 Overlay to ensure the container health is accurate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence doesn't read quite right.
design by using 's been trusted
Line 83. "Thankfully, we took this all in mind with our design by using 's been trusted"
…ns, including detailed descriptions for CLI, FPM, FPM-Apache, FPM-NGINX, and FrankenPHP. Marked NGINX Unit as deprecated and provided migration guidance. Improved overall structure for better user understanding.


4.0 Release: Say hello to FrankenPHP 👋
🚀 What this PR does
This PR is our home base for testing our new 4.0 release. This adds the highly anticipated variation of FrankenPHP and further optimizes serversideup/php to be highly optimized for deploying and maintaining Laravel applications.
🫵 WE NEED YOU: Help us test test this release
Important
Please keep reading the notes in this post before cowboy coding and throwing this into production 🤠
php-dev(notphp)View Test Images on Dockerhub →
Anything tagged with
283-will reference this PR and should be tested. Notice we're testing onphp-dev(notphp).🐛 Reporting Issues
👨🔬 What to test
We really need the community's help on testing these images as we progress towards stable.
1️⃣ All variations: Laravel Automations
There were huge improvements made to the 50-laravel-automations.sh script.
2️⃣ FrankenPHP: Caddyfile structure
LOG_LEVEL_OUTPUThelp you out? Are the logs too noisy?3️⃣ Test start up scripts
We made improvements to the entrypoint script.
🌎 Latest Documentation
Use the link below to reference the latest documentation (it will automatically update as we keep improving the docs).
View the latest documentation →
⚡️ What's new
🧟♂️ FrankenPHP variations now added
The highly anticipated release of FrankenPHP is now available. These images come with many enhancements compared to the official FrankenPHP images.
Images are unprivileged by default
For best security practices, we're running things as
www-data. This dramatically reduces your security footprint when running PHP in production. Because of this, we're listening on8080 (HTTP)and8443 (HTTPS). This follows the same design pattern as our other images.Native health checks
Health checks are critical for ensureing zero-downtime deployments. Our images come "batteries included" with intelligent health check endpoints that can easily be customized with
$HEALTHCHECK_PATH. By default, our images ensure/healthcheckis alive with Caddy, but you can change this variable toHEALTHCHECK_PATH=/upand it will use the built-in Laravel health check endpoint to ensure Laravel is actually ready to accept requests.Extremely flexible and production-grade Caddyfile by default
The default FrankenPHP Caddyfile gives you enough to get started, but we spent a ton of time making sure that we're shipping production-grade and secure configurations by default. This includes:
Designed for mass-scale production deployments
It's almost unbelievable and amazing how well FrankenPHP works with Caddy as a proxy. This tight integration allows you to do magical things like deploy trusted SSLs with Let's Encrypt. The only problem is, you probably have something else serving SSL termination and you most likely would not use that feature in a single container.
Our approach is "orchestrator first", meaning the image is designed for mass-scale in mind.
This means we're shipping the image assuming that you're doing TLS termination elsewhere. This makes it easier for you to scale and perform zero-downtime deployments:
flowchart TD A["Reverse Proxy (Not FrankenPHP)"] -->C{Container Service} C -->|STOP| D[MyApp:v1] C -->|START| E[MyApp:v2]Flexible environment configuration
Just like the experience with our other PHP variations, we also have things like
SSL_MODE,LOG_OUTPUT_LEVEL, changing PHP INI settings with environment variables, all our helper scripts for changing permissions, etc. that make it a breeze for you to customize how the PHP image behaves.More operating system variations
We are able to compile FrankenPHP by source, which allows us to open up support for many operating systems.
How tagging works
There's more to it, but in general the primary principle is:
{php-minor-version}-{variation}-{os-version}This means we're offering FrankenPHP with the following operating systems:
trixie: Debian Trixie (13)bookworm: Debian Bookworm (12)alpine3.22: Alpine 3.22alpine3.21: Alpine 3.21🌎 New Environment Variables
The following environment variables are now available:
default/dev/stdout100M/dev/stderrall/status0120nullOff🤩 New Features
Laravel Automations Script Improvements
The Laravel Automations script has been completely refactored to make it easier to support advanced Laravel features. Tons of new features are now available:
"php artisan optmize" now run by default
Instead of setting
AUTORUN_LARAVEL_ROUTE_CACHE,AUTORUN_LARAVEL_VIEW_CACHEetc, we useAUTORUN_LARAVEL_OPTIMIZEby default, which callsphp artisan optimize. Readjusting our logic to this new structure not only simplifies our approach to follow Laravel's best practices, it allows you to hook into the optimize command if you need to use it for your own application.If you don't want to use
php artisan optimizeor if you're running an older version of Laravel, no sweat! Our refactored approach is backwards compatible and you can enable/disable certain functions by just setting your desired values toAUTORUN_LARAVEL_ROUTE_CACHE,AUTORUN_LARAVEL_VIEW_CACHEetc.Added support for "migration modes"
We now support different migration modes of
refreshorfreshby Laravel. This is super helpful if you need to seed a preview environment.default(our default behavior)php artisan migrate- standard forward migrationsfreshphp artisan migrate:fresh- drops all tables and re-runs migrationsrefreshphp artisan migrate:refresh- rolls back and re-runs migrationsSpecify which database connections to run migrations with
If you run multiple databases with a multi-tenant Laravel application, you may need to specify your exact database connection that you'd like to use. We created
AUTORUN_LARAVEL_MIGRATION_DATABASEso you can set the configuration name of the database connection you'd like to run migrations on (ie.mysql). Supports running against multiple databases too (ie.mysql,pgsql).Added "--seed" option to migrations
Laravel has a helpful flag of
--seedthat you can run with php artisan migrate that will indicate if the seed task should be re-run. If you need this, just setAUTORUN_LARAVEL_MIGRATION_SEEDtotrue.Easier debugging
If you're running into issues with automations, set
AUTORUN_DEBUGtotrueand you'll get helpful output to help you figure out why you're running into issues.Control NGINX IP listening protocols with
NGINX_LISTEN_IP_PROTOCOLAre you running an IPv6 only cluster with fpm-nginx? Now you can set
NGINX_LISTEN_IP_PROTOCOL: ipv6and NGINX will listen on IPv6 stacks only. Same thing works if you set it toipv4, then IPv6 will be disabled.Great for Kubernetes clusters! 🤓
Default behavior is to keep a non-breaking change of
allwhich will listen on IPv4 and IPv6.🧘♂️ Quality Of Life Improvements
Improved health checks
A brilliant PR by @aSeriousDeveloper was merged which dramatically improves our "definition of healthy", especially on container start up. This approach utilizes
start-periodandstart-intervalwhich will give us more accurate readings and flexibility for container start up.unhealthy.Startup and Entrypoint Scripts
entrypoint.dscripts so we can gracefully handleexit 0in a entrypoint scriptChanging file permissions (
docker-php-serversideup-set-file-permissions)--serviceis now optional)--dirparameter for specifying extra directories (you can specify multiple--dirflags for multiple directories)Quiet health check access logs
fpm-nginxandfpm-apachelogs to never show access log output for any request$HEALTHCHECK_PATH. Things are much quieter now 😃🐛 Bug Fixes
All images
session.sid_bits_per_characterandsession.sid_length(using PHP defaults now) (session.sid_bits_per_character INI setting is deprecated & session.sid_length INI setting is deprecated #560)S6-based images (
fpm-nginxandfpm-apache)docker-serversideup-php-s6-initback for advanced S6 dependency use cases (Custom s6 services dependencies no longer works #479)fpm-nginx
absolute_redirect off;to have redirects return relative redirects (helpful for proxies like Traefik) (Add absolute_redirect off; to prevent broken redirects in containerized environments #567)svgzwith Symphony's asset mapper with FPM-NGINX (Move SVG handling to media assets block for Symfony's Asset Mapper compatibility #530)/package/admin/s6-overlay/libexec/preinit: info: /run belongs to uid X instead of Ywhen using thedocker-php-serversideup-set-file-permisisonsscript on FPM-NGINX Alpine instancesfpm-apache
⏫ Dependency updates
install-php-extensionsscript to v2.9.11✅ Jay's Checklist
These are notes to myself so I can remember where I left off as I start merging more things in:
Development
LOG_LEVEL_OUTPUTCaddyfileto work like ourfpm-nginximagePHP_environment variables from other Server Side Up images to work with FrankenPHPset-idandset-filepermissionsscripts work well$HEALTHCHECK_PATHDocumentation