Docker and the official php image
Updating PHP in Docker: fixed image tags, rebuilds and rollouts
To fix PHP2Shell in Docker, rebuild every image that carries PHP on a fixed release (8.5.12, 8.4.27, 8.3.36 or 8.2.35), then replace each running container with one started from the new image. Pulling a newer php image changes nothing by itself: a container that is already running keeps the PHP it started with.
Last reviewed against php.net, Docker's official PHP image documentation and the Docker and Kubernetes documentation.
Start here
What your image is built on decides the fix
Open the Dockerfile, or the image reference in your Compose file or Kubernetes manifest, and find which of these four you are dealing with. Most estates have more than one.
Built on the official php image
Your Dockerfile begins FROM php: followed by a tag. The tag decides which PHP release you get, so the fix is a tag change, a rebuild and a redeploy.
Built on a distribution image
Your Dockerfile begins FROM debian, ubuntu or another distribution and installs PHP with its package manager. The distribution's security update is your fix, and its version numbers are backported.
An application image someone else publishes
You run a vendor's or a project's image with PHP inside. Only the publisher can rebuild it, so you are waiting on their release and checking it when it lands.
Older images still in your registry
A rollback, a new environment or a forgotten deploy script can start a container from an image tag you built months ago. Rebuild or retire those tags as well as fixing what runs today.
Not sure which PHP releases fix the problem, or whether your branch is still supported? The PHP2Shell page has the full advisory, and the update hub covers servers that do not run containers.
Pinning
Fixed tags, floating tags and digests
How you name the base image decides whether a rebuild picks up a fix on its own, and whether you can tell afterwards what you shipped.
# Floating: whatever 8.3 release was newest when the image was last built
FROM php:8.3-fpm
# Fixed release: the Dockerfile records exactly what you ship
FROM php:8.3.36-fpm-trixie
# Fixed image: identical on every build until you change the digest
FROM php@sha256:<digest of the image you tested> - Floating tags such as
8.3-fpmonly help if you rebuild. An image built last month on8.3-fpmstill holds last month's PHP, whatever the tag points at today. - Full version tags make the change
visible: a one-line diff from
8.3.35to8.3.36is easy to review and easy to search for across repositories. Docker's documentation still treats every tag as mutable, because a publisher can point it at a new image. - Digests are the only reference
that never changes. Docker's documentation notes the trade-off:
you opt out of security fixes until you change the digest
yourself. Kubernetes' documentation advises against the
latesttag in production for the same reason of traceability, and suggests a meaningful tag, a digest or both.
Step by step
Rebuild the image, then replace the container
Docker images are snapshots. Nothing inside one changes until you build a new one, and nothing that is running changes until you start new containers from it.
-
1. Find every container that runs PHP
# Running containers, with the image each was started from and when docker ps # The same for a Compose project, from the project's folder docker compose ps # The PHP release inside one running container docker exec my-site php -vThe IMAGE and CREATED columns tell you what each container was started from and when. A container created before 21 October 2026 cannot be running a fixed official image.
-
2. Change the base image to a fixed release
# Before FROM php:8.3-fpm # After: the fixed release, written down FROM php:8.3.36-fpm-trixieStay on your branch and your Debian or Alpine release. A patch release is the smallest change that fixes PHP2Shell; moving branch or base operating system belongs in a separate, tested change.
-
3. Rebuild with a fresh pull, under a new tag
# --pull fetches the base image again instead of reusing the local copy docker build --pull -t registry.example.com/my-site:2026-10-27 . # Compose services that have a build section docker compose build --pullGive the result a tag of its own, such as the date, rather than overwriting the tag you deployed last time. That keeps the old image available for a rollback and stops any host or cluster node from confusing the two. Push the new tag to your registry so every environment deploys the same image.
-
4. Replace the running containers
# Recreate every container whose image has changed docker compose up -d # Recreate them even when Compose sees no change docker compose up -d --force-recreateWhen an image has changed, Compose stops the old container and creates a new one, keeping mounted volumes. Without Compose, stop and remove the old container and run a new one from the new tag.
-
5. Check the release inside the new container
Run
php -vinside it, as in confirm it worked below, before you move on to the next service.
Rollouts
Rolling out with Compose and Kubernetes
Both tools replace containers for you, but only when they can see that something changed. A rebuilt image under an unchanged name is the easiest way to fool them.
Docker Compose
For services with a build:
section, rebuild with docker compose build --pull,
then run docker compose up -d.
For services that run a published image directly, pull first:
# Services that run a published image directly (image:, no build:)
docker compose pull
docker compose up -d docker compose restart
is not a substitute: it restarts the existing containers on their
existing image.
Kubernetes
A Deployment rolls out new pods only when its pod template
changes, for example a new image reference. Re-pushing the same
tag changes nothing, and with any tag other than
latest the default
pull policy is IfNotPresent,
so a node can reuse its cached copy of the old image.
# Point the Deployment at the rebuilt image, under a new tag
kubectl set image deployment/my-site php=registry.example.com/my-site:2026-10-27
# Watch the old pods being replaced
kubectl rollout status deployment/my-site
# Return to the previous revision if the new image misbehaves
kubectl rollout undo deployment/my-site Red Hat's guidance for these flaws says the same thing in one line: replace the running container or pod.
Other orchestrators and hosted container platforms follow the same rule: deploy the new image reference, then check that no instance is still on the old one. Remember jobs and scheduled tasks that start from a PHP image only now and then; they pick up whatever image their definition names.
Patch these first
The apache variant runs mod_php with prefork
The php:*-apache images
contain Debian's Apache with PHP loaded as mod_php, and their
Dockerfile swaps Apache's event MPM for prefork. That is the
configuration the PHP project used to validate remote code
execution for CVE-2026-109618, and the PHP project rates mod_php
as the highest-risk way to run PHP for these flaws.
So rebuild apache-based images before anything else. Later, plan a move to the fpm variant behind a separate web server container, which is the arrangement php.net recommends for new installations; the Apache and mod_php guide explains why.
If you use the fpm variant
The fpm images listen on port 9000 for FastCGI. The image
documentation warns that FastCGI trusts whatever connects to
it, and tells you not to publish that port with
-p. Keep it on a private
container network shared with the web server only. The
PHP-FPM guide
covers pools and restarts in depth.
If you use the cli variant as a server
The cli image is the only one with the php-cgi binary, which some setups use to serve requests. php-cgi is on the PHP project's affected list too, so a cli-based image that serves web traffic needs the same rebuild.
Distribution-based images
Images that install PHP from a distribution
Some Dockerfiles skip the official php image and install the distribution's PHP instead. The official image's documentation points that way for anyone who wants Debian's PHP packages: start from a Debian image and install them directly.
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y php8.4-fpm
In an image like this, the fix arrives as the distribution's
security update, under the distribution's own version number. On
Debian 13 the fixed package is 8.4.27-1~deb13u1;
on Ubuntu 24.04 it is 8.3.6-0ubuntu0.24.04.13,
a backport that keeps the older upstream number. The
Linux guide
has the table for each release.
Defeat the build cache
Docker's documentation notes that the cache for a
RUN step is not invalidated
automatically between builds: it matches on the command text, and an
apt-get install line does not change
when a security update is published. A plain rebuild can therefore
produce an image with the old packages.
--no-cache runs every step again, and
--pull refreshes the Debian base.
Keep apt-get update and
apt-get install in the same
RUN line, as Docker's own guidance
recommends.
# --no-cache reruns apt-get, so the fixed packages are fetched
docker build --pull --no-cache -t registry.example.com/my-site:2026-10-27 .
# Check the exact package versions inside the new image
docker run --rm registry.example.com/my-site:2026-10-27 dpkg-query -W 'php8.4*' While images rebuild
Interim PHP settings in a container
If an image cannot be rebuilt on a fixed release straight away, the PHP project lists partial measures. It is explicit that there are no complete mitigations and that updating is the only safe choice, so treat these as a stopgap measured in days.
An ini file for PHP
; php2shell-interim.ini: a partial measure while you rebuild, not a fix
session.upload_progress.enabled = 0
; PHP 8.5 images only:
; fatal_error_backtraces = 0 A pool file for PHP-FPM
; zz-php2shell.conf: FPM images only, sorts after www.conf
[www]
pm.max_requests = 1 Copy both into the image
COPY php2shell-interim.ini $PHP_INI_DIR/conf.d/
COPY zz-php2shell.conf /usr/local/etc/php-fpm.d/ -
The official image reads extra ini files from
$PHP_INI_DIR/conf.d/, and FPM pool files from/usr/local/etc/php-fpm.d/in name order, with later files overriding earlier ones. Naming the pool file to sort afterwww.confkeeps your value in force. -
session.upload_progress.enabledcannot be switched off withini_set()in application code; it has to be set in configuration like this. The PHP project recommends leaving it off even after you patch, if nothing uses it. -
pm.max_requests = 1makes each FPM worker exit after one request, which the advisory suggests considering. Starting a fresh worker for every request costs performance, so expect more load while it is in place. - PHP reads these files when it starts, so rebuild and recreate the container for them to apply. A file mounted into a running container does nothing until PHP inside it starts again.
The full list of interim measures, including request size limits at the web server, is on the PHP2Shell page.
Confirm it worked
Check the PHP inside the container, not the Dockerfile
A Dockerfile says what you meant to ship. The container says what is actually serving traffic.
# A running container (container name)
docker exec my-site php -v
# A Compose service (service name)
docker compose exec php php -v
# A freshly built image, before it goes live
docker run --rm registry.example.com/my-site:2026-10-27 php -v - Official image: the command line
PHP is compiled in the same build as the image's FPM or Apache
module, so
php -vreports the release the container serves. Expect 8.5.12, 8.4.27, 8.3.36 or 8.2.35 for your branch. - Distribution image: the command
line and the web server's PHP come from separate packages, so
check every PHP package with
dpkg-query -Wand compare each version with the distribution's advisory. - Container age: in
docker ps, the CREATED time of each PHP container should be later than your rebuild. - From the outside: read the version the website itself reports, as described in how to check your PHP version, for at least one site per image.
- The registry: list the tags that could still be deployed, and delete or rebuild those on vulnerable releases.
On 26 October 2026 CISA added both CVEs to its Known Exploited Vulnerabilities catalogue. A replaced container starts clean, but data volumes, databases and anything the old container could reach do not. Signs a PHP site was compromised
Troubleshooting
When the new PHP does not show up
php -v inside the container still shows the old release
Either the container was never replaced, or the rebuild reused an old local copy of the base image. Rebuild with --pull, then recreate the container with docker compose up -d --force-recreate, and check again.
docker compose up -d reports every container as up to date
Compose only recreates a container when its configuration or image has changed. If the build produced the same image as before, nothing moves. Run docker compose build --pull first, or add --force-recreate.
Kubernetes pods keep running the old PHP after you pushed a rebuild
A Deployment only rolls out when its pod template changes, and re-pushing the same tag does not change it. With any tag other than latest, Kubernetes defaults to the IfNotPresent pull policy, so a node can start new pods from its cached old image. Push under a new tag or by digest, then run kubectl set image.
E: Package 'php-xxx' has no installation candidate
The official php image deliberately blocks Debian's PHP packages, so that one image does not end up with two PHP installations. Add extensions with docker-php-ext-install or pecl, or build FROM debian and use the distribution's PHP throughout.
An extension fails to build, or disappears, on the new tag
Extensions you compile in your Dockerfile are rebuilt against the new PHP. The image documentation recommends explicit version numbers in pecl install, and keeping the Debian suffix (such as -bookworm) you already use, so that a patch update does not also change the libraries underneath.
There is no php:8.x-apache-alpine tag
The official image publishes its apache variant on Debian only. Alpine tags exist for cli, fpm and zts. Moving from Debian to Alpine is a rebuild of a different kind, not part of a security patch.
Quick answers
Docker and PHP2Shell: questions, answered
Is pulling the latest php image enough to fix PHP2Shell?
No. docker pull refreshes the copy of the image stored on the host, and that is all it does. Your own image was built on the old base and has to be rebuilt, and every container started from it has to be replaced, because a container never changes image while it runs.
Should my Dockerfile say php:8.3-fpm or php:8.3.36-fpm-trixie?
A floating tag such as 8.3-fpm picks up the next patch release whenever you rebuild, but the Dockerfile no longer tells you which release you shipped, and the Debian release underneath can change with it. A full tag such as 8.3.36-fpm-trixie records the release and keeps the base steady, at the cost of changing the line yourself for every fix. Either way, Docker's documentation is clear that tags are mutable and only a digest is fixed.
Does docker compose restart load the new PHP?
No. It restarts the containers you already have, and Docker's own reference warns that changes to your Compose configuration are not picked up by it. After rebuilding or pulling, run docker compose up -d, which replaces containers whose image has changed.
Are the official image's Alpine tags fixed as well?
Yes. On 27 October 2026 the official image listed 8.5.12, 8.4.27, 8.3.36 and 8.2.35 as cli, fpm and zts tags on Alpine 3.24 and 3.23. If your image installs PHP from Alpine's own packages instead, those tags tell you nothing about it: check the version inside the container and Alpine's security information.
Is there a fixed official image for PHP 8.1 or 7.4?
No. The official image's supported tags now start at PHP 8.2. Docker Hub shows php:8.1-fpm was last pushed on 31 December 2025 and php:7.4-apache in November 2022, so those tags will not receive the fix. The PHP project publishes no release for 8.1 and older; move the image to a supported branch.
I run an application image that someone else publishes. What can I do?
Check the release inside it with docker exec and php -v, then look for the publisher's rebuilt image and its release notes. The NCSC asks organisations to watch vendors whose products bundle PHP. Until a fixed image exists, weigh whether the service should stay reachable from the internet.
Running PHP outside containers as well? See Linux servers, PHP-FPM and Apache.
Know your exposure
Find every PHP server you run
Patching the server you know about is the easy part. A vulnerability scan finds the forgotten staging site, the old microsite and the appliance with PHP inside, then tells you which ones are still exposed.