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.

3D shield with a green tick above a device, for images built from the official PHP image

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.

3D server rack, for images that install PHP from distribution packages

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.

3D ring of connected devices, for third-party images with PHP inside

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.

3D secure vault, for old image tags kept in a registry

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.

The official php image

Reading the php image's tags

Every tag of the official php image combines three choices: the PHP release, the variant and the base operating system. On 27 October 2026 all four fixed releases (8.5.12, 8.4.27, 8.3.36 and 8.2.35) were published in every variant on Debian 13 (trixie) and Debian 12 (bookworm), and as cli, fpm and zts tags on Alpine 3.24 and 3.23.

The four variants

Variant What is inside A fixed tag
cli The PHP command line and nothing else: no web server. It is also the only variant that ships the php-cgi binary. php:8.3.36-cli-trixie
apache Debian's Apache httpd with PHP loaded as mod_php, switched to the prefork MPM. Published on Debian only: there is no Alpine apache tag. php:8.4.27-apache-trixie
fpm PHP-FPM listening on port 9000, for a web server such as nginx that runs in a container of its own. php:8.5.12-fpm-alpine3.24
zts PHP compiled with Zend Thread Safety (ZTS), the thread-safe build, with no web server included. php:8.2.35-zts-bookworm

Every variant includes the PHP command line at /usr/local/bin/php, which is what makes the checks further down possible.

How much each tag pins down

Shorter tags are aliases that move as new images are published. This is how the fpm tags resolved on 27 October 2026.

Tag What it fixes in place On 27 October 2026
php:8.3.36-fpm-trixie One PHP release, one variant, one Debian release. PHP 8.3.36 on Debian 13 (trixie)
php:8.3.36-fpm One PHP release and variant; the Debian release is whichever the image currently uses. Same image as php:8.3.36-fpm-trixie
php:8.3-fpm The newest 8.3 release. Moves each time a new 8.3 patch release is published. Same image as php:8.3.36-fpm-trixie
php:8-fpm, php:fpm The newest PHP 8 release on any branch, so a rebuild can jump from one branch to the next. PHP 8.5.12 on Debian 13
php:latest The newest cli image. Not a web server image at all. PHP 8.5.12 cli on Debian 13

No tags for PHP 8.1 or older. The supported tag list starts at PHP 8.2. Older tags can still be pulled, but Docker Hub shows php:8.1-fpm was last pushed on 31 December 2025, so it will never carry the fix. PHP 8.2's own support ends on 31 December 2026; see PHP end of life dates.

The 8.6 release candidate tags. Tags such as 8.6.0RC4-fpm also exist. PHP 8.6.0 RC4 carries the same fixes, but it is a pre-release and the PHP project does not intend it for production.

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-fpm only help if you rebuild. An image built last month on 8.3-fpm still 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.35 to 8.3.36 is 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 latest tag 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. 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 -v

    The 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. 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-trixie

    Stay 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. 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 --pull

    Give 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. 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-recreate

    When 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. 5. Check the release inside the new container

    Run php -v inside 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.

3D network of laptops and servers, for a Docker Compose rollout

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.

3D network of connected machines, for a Kubernetes rollout

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 after www.conf keeps your value in force.
  • session.upload_progress.enabled cannot be switched off with ini_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 = 1 makes 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 -v reports 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 -W and 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.

PHP end of life dates

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.

Should I take my website offline?

Running PHP outside containers as well? See Linux servers, PHP-FPM and Apache.

3D rocket illustration above the call to find every exposed PHP server

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.