Major version migrations
When we ship a new major version of serversideup/php, we collect the breaking changes, new features, and migration checklists in this guide. Use it whenever you're crossing a major version boundary — for example, V3 → V4 or V2 → V3. For day-to-day patches and security updates, see the Upgrade Guide instead.
Dropped PHP versions
We stop building a PHP version once there is no longer a base image we can produce a patched build from. The tags stay pullable, but they freeze at their last successful build and receive no further security updates.
| Version | Last built | Why |
|---|---|---|
| PHP 8.1 | 2025-12-16 | Reached end of security support and upstream removed the 8.1 branch. |
| PHP 8.0 | 2026-09-03 | Only ever shipped on Debian Bullseye and Alpine 3.16, both now EOL. Upstream stopped building it in November 2023. |
| PHP 7.4 | 2026-09-03 | Same bases as 8.0. Upstream stopped building it in November 2022. |
Debian Bullseye and Alpine 3.16 were dropped at the same time. Debian 11 reached end of LTS on 2026-08-31, and its final bullseye-security release file expired on 2026-09-07, so apt-get update no longer succeeds inside a build.
If you are on one of these, move to PHP 8.2 or newer on bookworm, trixie, alpine3.23, or alpine3.24. See EOL versions and the legacy-modernization path.
Version 4 → Version 5 Migration
Version 5 is about OPcache. Setting PHP_OPCACHE_ENABLE=1 now gives you tuned defaults instead of PHP's stock values. There is one breaking change, and it only affects you if you run with OPcache enabled while your code is mounted as a volume.
If you want to stay on Version 4 while you review the changes, pin your image tag to the last v4 release. Version-pinned tags are never rebuilt, so you will not receive security updates until you move to v5. See how our releases work.
services:
php:
image: serversideup/php:8.5-fpm-nginx-v4.5.1
Why we changed OPcache
Most people start with these images in development, so OPcache stays off by default to keep your edits showing up instantly. But when you flip it on for production, the settings behind it should be the ones you would have picked yourself after reading the docs. They were not. Version 4 checked every cached file for changes every two seconds, shipped PHP's stock memory sizes, and documented an environment variable that did nothing. Version 5 fixes all of that with the values from Symfony's performance guide, which says "The default OPcache configuration is not suited for Symfony applications." FrankenPHP's performance guide points to the same page "even if you don't use Symfony." Read the production performance tuning guide →
Breaking changes in Version 5
PHP_OPCACHE_ENABLE=1.PHP_OPCACHE_VALIDATE_TIMESTAMPS now defaults to 0
With OPcache enabled, PHP files are now cached until the container restarts. PHP no longer checks the filesystem for changes on every request. This is the correct setting for code that is built into the image, which is how we recommend deploying.
You are affected if you set PHP_OPCACHE_ENABLE=1 and any of these apply:
- Your code is mounted as a volume and you edit it in place
- You follow the volume-based WordPress approach and update with
git pull, WP-CLI, or SFTP - You run commands like
docker exec php artisan optimizeagainst a live container and expect the web workers to pick up the new files
The fix is one of two things: restart the container after code changes (recommended), or set PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 to restore the Version 4 behavior.
Fixes
PHP_OPCACHE_FORCE_RESTART_TIMEOUTexisted in Version 4 but never reachedphp.ini. It now works. The default of180matches PHP's own default, so nothing changes unless you had set it to something else.
Changed defaults
The OPcache values apply only when PHP_OPCACHE_ENABLE=1, and the memory is only used as files are cached. PHP_REALPATH_CACHE_TTL is not an OPcache setting and applies whether OPcache is on or off. It comes from the same Symfony recommendation.
| Variable | Version 4 | Version 5 |
|---|---|---|
PHP_OPCACHE_VALIDATE_TIMESTAMPS | 1 | 0 |
PHP_OPCACHE_MEMORY_CONSUMPTION | 128 | 256 |
PHP_OPCACHE_INTERNED_STRINGS_BUFFER | 8 | 32 |
PHP_OPCACHE_MAX_ACCELERATED_FILES | 10000 | 32531 |
PHP_REALPATH_CACHE_TTL | 120 | 600 |
New variables
PHP_OPCACHE_ENABLE_CLI- Whether CLI commands use OPcache whenPHP_OPCACHE_ENABLE=1. Defaults to1, which is what Version 4 did. Set it to0to keep OPcache on for the web server only.PHP_OPCACHE_PRELOAD- Path to a preload script. Symfony generates one for you and recommends it.PHP_OPCACHE_PRELOAD_USER- The user to preload as when the container runs as root.
See the full list of environment variables →
V5 Migration Checklist
Docker Compose
- Update the image tag
- If your code is mounted as a volume with
PHP_OPCACHE_ENABLE=1, either turn OPcache off for that environment or addPHP_OPCACHE_VALIDATE_TIMESTAMPS=1 - If you deploy WordPress on a volume, add
PHP_OPCACHE_VALIDATE_TIMESTAMPS=1or restart the container after updates made outside the WordPress admin - Replace any
docker exec ... artisan optimizestyle deployment steps with a container restart
Dockerfile
- Nothing is required
- If you add
PHP_OPCACHE_PRELOAD, prefer setting it on the running service rather than as anENVin the Dockerfile, so build steps likeRUN composer installdo not depend on the preload script
Version 3 → Version 4 Migration
Version 3 to Version 4 is a much easier migration compared to previous versions. There are no breaking changes, so you can simply update your image tag to the latest version and take advantage of the new features.
New Features in Version 4
This release focused on expanding image variations, improving Laravel automations, and enhancing the developer experience. Here are the key features:
- FrankenPHP variation - A new production-ready FrankenPHP variation with intelligent defaults, flexible environment configuration, native health checks, and support for Debian and Alpine operating systems.
- Revamped documentation site - Completely rewritten documentation with improved navigation, better examples, and a modern user experience.
- Enhanced Laravel automations - Refactored to use
php artisan optimizeby default (following Laravel best practices), with support for migration modes (fresh,refresh), database connection selection, seeding options, and easier debugging withAUTORUN_DEBUG. - Expanded environment variables - 25+ new environment variables for fine-tuning PHP, NGINX, Apache, and FrankenPHP configurations. See the full list of environment variables →
- Improved health checks - Better container startup detection using
start-periodandstart-intervalfor more accurate health readings. - IPv6 support for NGINX - Control IP listening protocols with
NGINX_LISTEN_IP_PROTOCOL(supportsipv4,ipv6, orall). - Enhanced file permissions script -
docker-php-serversideup-set-file-permissionsnow includes automated service detection and support for multiple directories with the--dirflag. - Quieter logs - Health check requests no longer appear in access logs for
fpm-nginxandfpm-apachevariations.
Quality of Life Improvements
- Startup scripts - Improved handling of
entrypoint.dscripts with better error handling and a redesigned container startup info display. - FPM process control - Default changed to
ondemandfor even lower resource usage infpm-nginxandfpm-apachevariations. - Better Apache logs - Access logs now include "Referer" and "User Agent" for better debugging.
- NGINX improvements - Added
absolute_redirect off;for better proxy compatibility, fixedsvgzhandling with Symfony's asset mapper, and allowedrobots.txtto be dynamically generated by PHP.
V4 Migration Checklist
Since there are no breaking changes, the migration is straightforward:
Update Your Images
Simply update your image tags to the latest version. For example:
services:
php:
image: serversideup/php:8.5-fpm-nginx
No other changes are required unless you want to take advantage of new features.
Optional: Leverage New Features
Consider enabling Laravel optimizations (if using Laravel):
services:
php:
image: serversideup/php:8.5-fpm-nginx
environment:
AUTORUN_ENABLED: "true"
AUTORUN_LARAVEL_OPTIMIZE: "true"
Try the new FrankenPHP variation:
services:
php:
image: serversideup/php:8.5-frankenphp
ports:
- 80:8080
- 443:8443
That's it! Version 4 is designed to be a smooth, non-breaking upgrade that gives you more flexibility and features when you need them.
Version 2 → Version 3 Migration
If you're an existing user of our v2 images, be sure that your current configurations are NOT set to use the latest images. To do this, you can lock your images into the v2.2.1 tag. This will ensure that you're not automatically upgraded to the v3 images.
For example, if you are using 8.2-fpm-nginx, you would change your compose.yml file to use the v2.2.1 tag:
services:
php:
image: serversideup/php:8.2-fpm-nginx
ports:
- 80:80
volumes:
- .:/var/www/html
services:
php:
image: serversideup/php:8.2-fpm-nginx-v2.2.1
ports:
- 80:80
volumes:
- .:/var/www/html
All you need to do is add -v2.2.1 to the end of the image tag. This will ensure that you're not automatically upgraded to the v3 images.
New Features in Version 3
We've been busy overhauling our PHP Docker Images to make them more production-ready and easier to use. Here are some of the new features we've added:
- Based on official PHP Images - We're now building an improved developer experience on top of the official PHP Docker images.
- Unprivileged by default - We're now running our images as an unprivileged user by default. This is a huge step forward in security and compatibility.
- PHP 8.4 support - We're now shipping the latest and greatest.
- Pin to the exact minor version - Pin your app to the exact minor version of PHP that you want to use. This means you can pin to
8.2.12instead of8.2. - Easier start up script customization - We now have a folder called
/etc/entrypoint.dthat allows you to easily customize your container with scripts. Just put them in numerical order and we'll execute any shell script you want. No S6 Overlay knowledge required. - Expanded Laravel Automations - We added automations to run
config:cache,route:cache,view:cache,event:cache,migrate --force --isolated, andstorage:link - NGINX Unit Support - We're offering NGINX Unit as a variation as an alternative to PHP-FPM. This allows you to run PHP applications without the need for a webserver like NGINX or Apache to run with PHP-FPM.
- Available on GitHub Packages - We're now publishing our images to GitHub Packages. This means you can use our images without needing to authenticate with Docker Hub.
Breaking changes in Version 3
Ubuntu is no longer used as a base image
We now use Debian or Alpine as our base OS (because we're using the official PHP images as a base). This is a huge change, but we're confident this will be the best direction moving forward.
ppa:ondrej/php is no longer used
Since we're using PHP.net as the "official source of truth" for getting our PHP versions, this means we're also dropping support for the ppa:ondrej/php repository. If you're using things like apt-get install php-redis you will need to change your method of installing PHP extensions.
Learn how to install your own PHP extension →
webuser is no longer being used
We used to add a user called webuser with the UID of 9999 with shell permissions. To increase security, we're now using the www-data user and group that is built into the official PHP images. If you have mounted volumes, you will need to chown the files to match the ID of the www-data user and groups. For Debian, this is 33:33 and for Alpine, this is 82:82.
NGINX and Apache listen on 8080 (HTTP) and 8443 (HTTPS) by default
Our images are now unprivileged by default. This is a major step forward in security and compatibility. Since we are unprivileged by default, we lose the ability to mount on ports less than 1024. If you're using NGINX or Apache, you will need to update your port mappings to use 8080 and 8443 instead of 80 and 443.
Learn more about this change →
S6 Overlay is only used in *-fpm-apache and *-fpm-nginx images
Due to compatibility issues, we only use S6 Overlay in our *-fpm-apache and *-fpm-nginx images. If you were using S6 Overlay for our other variations (cli, fpm, etc), you will need to migrate your scripts to use the new /etc/entrypoint.d folder.
SSL_MODE is now set to off by default (HTTP only)
Running end-to-end SSL by default created more problems than good. By default, we're now shipping HTTP-only by default with the option for people to turn this on.
AUTORUN_ENABLED is now set to false by default.
Having this set to "true" by default also created more problems than good. If you want to use any of the Laravel Automation Scripts, be sure to set this to true.
MSMTP is no longer included in the images
For security and image size reasons, we removed MSMTP from the images. If you need to send emails, use an external SMTP service like Postmark/Sendgrid/Mailgun. You can also extend the image yourself to include MSMTP specifically for your use case.
Variable deprecations
WEB_APP_DIRECTORYhas now been renamed toAPP_BASE_DIRDEBUG_OUTPUThas been removed for in favor ofLOG_OUTPUT_LEVEL=debugPUID&PGIDare no longer used because it requires root privileges. See the new way to set the UID and GID →MSMTP_RELAY_SERVER_HOSTNAME&MSMTP_RELAY_SERVER_PORTare no longer used because MSMTP is no longer included in the images.PHP_POOL_NAMEhas been renamed toPHP_FPM_POOL_NAME
V3 Migration Checklist
Here is a good list to perform the V3 migration.
Repository
- Ensure you're committing to a test environment
Docker Compose
- Update the image name (if applicable)
- Check each environment variable exists and is set to a proper value See the full list of environment variables →
- Ensure you updated the ports to
8080and8443for NGINX, Apache, and Unit - Consider adding
PHP_OPCACHE_ENABLE=1to your production environment for increased performance
Dockerfile
- Update the base image name (if applicable)
- Remove any
ppa:ondrej/phpreferences - Remove any Ubuntu specific commands
- Ensure all extensions are installed with the
install-php-extensionscommand Learn how to install your own PHP extension → - Ensure your
COPYcommands are copying with the correct permissions (i.e.--chown=www-data:www-data)
CI/CD
If you're running fpm-nginx (or similar) on a runner that's running as your builds as root, you may need to add user = www-data and group = www-data to your php-fpm.conf file so you can bring FPM up correctly.
If you have to run things as root in CI, you can do this with a multi stage build and set the targets:
############################################
# Base Image
############################################
# Learn more about the Server Side Up PHP Docker Images at:
# https://serversideup.net/open-source/docker-php/
FROM serversideup/php:8.4-fpm-nginx AS base
## Uncomment if you need to install additional PHP extensions
# USER root
# RUN install-php-extensions bcmath gd
############################################
# Development Image
############################################
FROM base AS development
# We can pass USER_ID and GROUP_ID as build arguments
# to ensure the www-data user has the same UID and GID
# as the user running Docker.
ARG USER_ID
ARG GROUP_ID
# Switch to root so we can set the user ID and group ID
USER root
RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \
docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID
USER www-data
############################################
# CI image
############################################
FROM base AS ci
# Sometimes CI images need to run as root
USER root
############################################
# Production Image
############################################
FROM base AS deploy
COPY --chown=www-data:www-data . /var/www/html
USER www-data
Production/Staging Servers
- Update all host volume file permissions to match the
www-dataUID/GID (33:33for Debian,82:82for Alpine) Learn how to manage file permissions - If you're running Docker Swarm with host volume mounts, we created a script that could potentially help (change-volume-permissions.sh)
Deployment
- CI/CD with valid tests is always encouraged
- After completing all steps above, you're now ready to deploy the new images
Configuring Trusted Proxies
Learn how to configure trusted proxies to get accurate client IP addresses when running behind load balancers, CDNs, or reverse proxies.
Production performance tuning
The settings that matter for PHP performance in production, where our defaults come from, and how to measure and tune them for your application.