Say hello to FrankenPHP 👋
😅 Breaking Changes
- NONE
We went through four beta releases and 6 weeks of extensive testing. Thank you beta testers ❤️
⚠️ Special places to look (depending on your use case)
- If you use custom start up scripts, just note the behavior of
exit 0coming back. We made changes to the subshell stuff. You can learn more about why we did this in #481
✋ Deprecation Notices
- Added a deprecation notice to Unit because NGINX is no longer maintaining the project
⚡️ 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 on 8080 (HTTP) and 8443 (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 /healthcheck is alive with Caddy, but you can change this variable to HEALTHCHECK_PATH=/up and 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:
- Native CloudFlare support with trusted IP addresses
- Performance and caching rules made available by default
- Security headers included by default
- Flexible and powerful logging defaults
- Simple and intelligent self-signed certificate generation (but still allowing you to use Let's Encrypt if you wanted)
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
🚀 Laravel Octane Support
Laravel Octane is now natively supported with our FrankenPHP variation. We created a native health check script to ensure your container is healthy when running Octane and also documented how you can add Octane to your project.
Learn more about Laravel Octane →
🌐 New Documentation Site
We completely rewrote our documentation site and improved the user experience dramatically. Not only did we add a ton of new documentation, we used many native Nuxt Content components to improve the experience when reading the docs.
It also includes native LLM integration 👀
🤩 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_CACHE etc, we use AUTORUN_LARAVEL_OPTIMIZE by default, which calls php 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 optimize or 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 to AUTORUN_LARAVEL_ROUTE_CACHE, AUTORUN_LARAVEL_VIEW_CACHE etc.
Added support for "migration modes"
We now support different migration modes of refresh or fresh by Laravel. This is super helpful if you need to seed a preview environment.
| Migration Mode | Description |
|---|---|
default (our default behavior) |
Runs php artisan migrate - standard forward migrations |
fresh |
Runs php artisan migrate:fresh - drops all tables and re-runs migrations |
refresh |
Runs php artisan migrate:refresh - rolls back and re-runs migrations |
Specify 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_DATABASE so 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 --seed that you can run with php artisan migrate that will indicate if the seed task should be re-run. If you need this, just set AUTORUN_LARAVEL_MIGRATION_SEED to true.
Easier debugging
If you're running into issues with automations, set AUTORUN_DEBUG to true and 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_PROTOCOL
Are you running an IPv6 only cluster with fpm-nginx? Now you can set NGINX_LISTEN_IP_PROTOCOL: ipv6 and NGINX will listen on IPv6 stacks only. Same thing works if you set it to ipv4, then IPv6 will be disabled.
Great for Kubernetes clusters! 🤓
Default behavior is to keep a non-breaking change of all which will listen on IPv4 and IPv6.
Change listening ports for NGINX and Apache
To mimic FrankenPHP + Caddy, we created NGINX_HTTPS_PORT and APACHE_HTTPS_PORT if you want to change the listening port of Apache or NGINX for any reason
🧘♂️ 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-period and start-interval which will give us more accurate readings and flexibility for container start up.
| Option | Description | Old Value | New Value |
|---|---|---|---|
| start-period | start period provides initialization time for containers that need time to bootstrap. Probe failure during that period will not be counted towards the maximum number of retries. However, if a health check succeeds during the start period, the container is considered started and all consecutive failures will be counted towards the maximum number of retries. | - | 60s |
| start-interval | start interval is the time between health checks during the start period. | - | 3s |
| timeout | If a single run of the check takes longer than timeout seconds then the check is considered to have failed. | 3s | 3s |
| retries | It takes retries consecutive failures of the health check for the container to be considered unhealthy. |
3 | 3 |
| interval | The health check will first run interval seconds after the container is started, and then again interval seconds after each previous check completes. | 5s | 10s |
Startup and Entrypoint Scripts
- Changed approach to executing
entrypoint.dscripts so we can gracefully handleexit 0in a entrypoint script - Re-designed container start up info script
Changing file permissions (docker-php-serversideup-set-file-permissions)
- Added automated service detection (
--serviceis now optional) - Added
--dirparameter for specifying extra directories (you can specify multiple--dirflags for multiple directories)
Quiet health check access logs
- Improved
fpm-nginxandfpm-apachelogs to never show access log output for any request$HEALTHCHECK_PATH. Things are much quieter now 😃
🐛 Bug Fixes
All images
- Fixed deprecation notices for
session.sid_bits_per_characterandsession.sid_length(using PHP defaults now) (#560) - Removed sub shell behavior on entrypoint scripts to support
exit 0(#481)
S6-based images (fpm-nginx and fpm-apache)
- Re-added
docker-serversideup-php-s6-initback for advanced S6 dependency use cases (#479)
fpm-nginx
- Added
absolute_redirect off;to have redirects return relative redirects (helpful for proxies like Traefik) (#567) - Fixed a bug with
svgzwith Symphony's asset mapper with FPM-NGINX (#530) - Fixed notice of
/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 instances - Allow robots.txt to be dynamically generated by PHP (#589)
- Set default FPM process control to
ondemandfor even lower resource usage by default (#594)
fpm-apache
- Added "Referer" and "User Agent" in Apache access logs (#540)
- Set default FPM process control to
ondemandfor even lower resource usage by default (#594)
⏫ Dependency updates
- Updates
install-php-extensionsscript to v2.9.18
🌎 New Environment Variables
The following environment variables are now available:
| Environment Variable | Default | Authored By |
|---|---|---|
| APACHE_HTTP_PORT | 8080 | @jaydrogers |
| APACHE_HTTPS_PORT | 8443 | @jaydrogers |
| AUTORUN_DEBUG | false | @jaydrogers |
| AUTORUN_LARAVEL_OPTIMIZE | true | @aSeriousDeveloper |
| AUTORUN_LARAVEL_MIGRATION_FORCE | true | @jaydrogers |
| AUTORUN_LARAVEL_MIGRATION_MODE | default |
@jaydrogers |
| AUTORUN_LARAVEL_MIGRATION_SEED | false | @jaydrogers |
| AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK | false | @jaydrogers |
| NGINX_ACCESS_LOG | /dev/stdout |
@robsontenorio |
| NGINX_CLIENT_MAX_BODY_SIZE | 100M |
@dlundgren |
| NGINX_ERROR_LOG | /dev/stderr |
@robsontenorio |
| NGINX_HTTP_PORT | 8080 |
@jaydrogers |
| NGINX_HTTPS_PORT | 8443 |
@jaydrogers |
| NGINX_LISTEN_IP_PROTOCOL | all |
@yuuzukatsu, @jaydrogers |
| PHP_FPM_PM_MAX_REQUESTS | 0 | @ifaridjalilov, @thueske |
| PHP_FPM_PM_STATUS_PATH | /status |
@jaydrogers |
| PHP_MAX_INPUT_VARS | 1000 | @RadeJR |
| PHP_OPCACHE_ENABLE_FILE_OVERRIDE | 0 |
@jaydrogers |
| PHP_OPCACHE_FORCE_RESTART_TIMEOUT | 180 | @aSeriousDeveloper, @jaydrogers |
| PHP_OPCACHE_JIT | off | @aSeriousDeveloper, @jaydrogers |
| PHP_OPCACHE_JIT_BUFFER_SIZE | 0 | @aSeriousDeveloper, @jaydrogers |
| PHP_OPCACHE_SAVE_COMMENTS | 1 | @aSeriousDeveloper, @jaydrogers |
| PHP_OPCACHE_VALIDATE_TIMESTAMPS | 1 | @aSeriousDeveloper, @jaydrogers |
| PHP_REALPATH_CACHE_TTL | 120 |
@jaydrogers |
| PHP_ZEND_DETECT_UNICODE | null |
@jaydrogers |
| PHP_ZEND_MULTIBYTE | Off |
@jaydrogers |
🏆 New Contributors
- @twiesing made their first contribution in #513
- @aSeriousDeveloper made their first contribution in #510
- @guillaumebriday made their first contribution in #487
- @DarkGhostHunter made their first contribution in #404
- @hookenz made their first contribution in #527
- @robsontenorio made their first contribution in #534
- @yuuzukatsu made their first contribution in #539
- @dlundgren made their first contribution in #558
- @ricardomm85 made their first contribution in #530
- @arnaud-ritti made their first contribution in #571
- @kohenkatz made their first contribution in #602
- @ikerls made their first contribution in #608
Full Changelog: v3.5.2...v4.0.0
