Vulnerable PHP Docker Images: Rebuild Guide After PHP2Shell
A simple `docker pull` won't fix PHP2Shell. Learn how to rebuild php:8.x and FrankenPHP images and update your CI/CD pipeline to deploy the patch to production.
by Cleverson Gouvêa

A vulnerable PHP Docker image doesn't fix itself when PHP releases a patch. The production container continues running the binary it was created with, even after Docker Hub publishes the new version. In this guide, I'll show you how to find every image affected by PHP2Shell, how to properly rebuild php:8.x and FrankenPHP images, what to change in your CI/CD pipeline, and why container memory limits don't fully address the flaw.
TL;DR
- PHP2Shell consists of two critical vulnerabilities (CVE-2026-109618 and CVE-2026-109619, CVSS 4.0 = 9.2) that allow remote code execution without authentication. The fix was released on 10/21/2026 in versions 8.5.12, 8.4.27, 8.3.36, and 8.2.35.
- Official
php:8.ximages with the patch have been on Docker Hub since 10/21. FrankenPHP fixed it in v1.13.2, released the same day.docker pulldoesn't fix anything on its own. You need to rebuild with a new base, push, and recreate each container.- Old fixed tags, pinned digests, and build cache can unknowingly keep the old version.
- Container memory limits (cgroup) protect availability; they don't close the vulnerability. The other CVE doesn't even depend on memory.
Why a Vulnerable PHP Docker Image Remains Vulnerable After the Patch
On a traditional server, apt upgrade replaces the package, and restarting PHP-FPM solves it. In a container, the logic is different: the image is immutable. The PHP running inside was compiled and frozen at build time.
This creates three layers that need updating, in this order:
- The base image (
php:8.4-fpm,php:8.3-apache,dunglas/frankenphp) needs to have the corrected version. - Your application's image, which inherits from the base via
FROM, needs to be rebuilt and pushed to the registry. - Running containers need to be recreated from the new image.
Skipping any layer leaves a vulnerable PHP Docker image in production, appearing updated. The most common scenario I see is a team running docker pull on the server, seeing "Downloaded newer image," and closing the ticket. The new base is on disk, but the old container remains running, and the application image wasn't even rebuilt.
I've worked with PHP since 2008 and use containers daily, from development environments with DDEV to the custom software development services we deliver. The difference between an applied patch and a "downloaded" patch is always this: the process that actually serves requests.
PHP2Shell in Two Minutes: What Changed Between October 17 and 23
PHP2Shell is the name given to two flaws in the PHP core. The official php.net advisory states that "all PHP installations serving web requests" are at risk, citing PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed. The greatest risk, according to PHP itself, is mod_php: there, the attack triggers before any application code runs.
- CVE-2026-109618 (memory exhaustion corruption): PHP runs out of memory in the middle of request assembly, and cleanup treats partial data as valid.
- CVE-2026-109619 (stale global state, a use-after-free): an internal state survives after memory has been freed, and the next request in the same worker uses this leftover data.
The timeline explains the urgency. According to the Tenable FAQ, Amazon observed an exploitation attempt on 10/17. Details leaked on 10/20, and the patch was released on 10/21. Palo Alto Networks identified over 22.6 million exposed instances, according to TechTarget. On 10/23, PHP published an additional alert (CVE-2026-115560) regarding an unlikely vector related to session upload progress, which will be fixed in the next regular release.
| PHP Branch | PHP Status | Official php Image on Docker Hub |
|---|---|---|
| 8.5 | Fixed in 8.5.12 | Updated on 10/21/2026 |
| 8.4 | Fixed in 8.4.27 | Updated on 10/21/2026 |
| 8.3 | Fixed in 8.3.36 | Updated on 10/21/2026 |
| 8.2 | Fixed in 8.2.35 (end-of-life on 12/31/2026) | Updated on 10/21/2026 |
| 7.4, 8.0 and 8.1 | Only minimal source code backports | No new image: the 8.1-fpm tag hasn't received a push since 12/31/2025 |
| 5.4 to 7.3 | No fix | Abandoned (the 7.4-fpm stopped on 11/15/2022) |
I checked the dates in the last column directly against the Docker Hub API and the official image repository. The 8.4.27-fpm tag received its first push on 10/21, at 4:43 PM (UTC).
How to Discover Which Images Are Vulnerable
Before rebuilding, inventory every vulnerable PHP Docker image you have. In companies with multiple projects, a forgotten container always appears: the old dashboard, the queue worker, the staging environment that remained exposed.
Ask the Container, Not the Host
The host's PHP says nothing about the container's PHP, and a vulnerable PHP Docker image might be running on a server whose php -v shows 8.4.27. Also, don't just rely on the CLI's php -v: Datadog warns that the command-line installation might differ from the web-serving runtime. Run the check inside each service:
# version of PHP in each running container
for c in $(docker ps -q); do
echo "$(docker inspect --format '{{.Name}}' $c): $(docker exec $c php -r 'echo PHP_VERSION;' 2>/dev/null || echo 'no php')"
done
# image the container uses vs. newest image on disk
docker inspect --format '{{.Image}}' my-container
docker image ls --digests php
Look at the Registry, Not Just the Servers
Official images compile PHP from source code and record the version in the PHP_VERSION variable. You can audit without spinning anything up:
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' registry.example.com/app:prod | grep PHP_VERSION
A detail that trips up many people: since the PHP in the official image doesn't come from a Debian or Alpine package, scanners that only read the system's package database might not see it. Datadog itself recommends a separate inventory for compiled runtimes. If your vulnerable PHP Docker image doesn't appear in the scanner report, that doesn't prove it's secure.
What Goes on the List
- Web containers (FPM, Apache, FrankenPHP).
- Queue workers, cron jobs, and consumers that use the same image.
- Staging and preview images generated by CI.
- Third-party images (dashboards, internal tools) that embed PHP. This is often the vulnerable PHP Docker image no one remembers to rebuild.
Rebuilding php:8.x Images: The Step-by-Step Guide That Works
With the inventory complete, correcting a vulnerable PHP Docker image follows a short sequence. What changes from project to project is how the FROM instruction is written.
Floating Tag, Fixed Tag, or Digest
How FROM is Written |
Example | Receives Patch Automatically? | What to Do |
|---|---|---|---|
| Branch Tag | php:8.4-fpm |
Yes, on the next build with --pull |
Rebuild with --pull |
| Exact Tag | php:8.4.26-fpm |
No | Edit to 8.4.27-fpm and rebuild |
| Digest | php:8.4-fpm@sha256:... |
No | Update the digest and rebuild |
| Unsupported Branch | php:8.1-fpm |
Never | Migrate the application to 8.2+ |
Exact tags and digests are good reproducibility practices. The cost is precisely this: in an urgent fix, someone needs to edit the file. If your project uses a digest, the vulnerable PHP Docker image remains locked until a human (or a dependency bot) changes the line.
The Commands
# 1. rebuild with new base (and no cache, if cache holds the old version)
docker build --pull --no-cache -t registry.example.com/app:2026-10-24 .
# 2. check the version BEFORE publishing
docker run --rm registry.example.com/app:2026-10-24 php -r 'echo PHP_VERSION, PHP_EOL;'
# 3. publish and recreate containers
docker push registry.example.com/app:2026-10-24
docker compose pull && docker compose up -d --force-recreate
# 4. validate what's running
docker compose exec web php -r 'echo PHP_VERSION;'
The --no-cache is important. Without it, a cached layer might reuse the old base, and the build finishes "green" with the same vulnerable PHP Docker image as before. Recreating the container restarts all PHP workers. Mounted volumes remain intact.
Variants: Update All
Each variant is a separate image: -fpm, -apache, -cli, -zts, Debian (bookworm, trixie), and -alpine. Start with the -apache variants, which use mod_php, the scenario PHP considers highest risk. And do not publish the FPM port (9000) outside the internal network: FastCGI was not designed to be exposed.
FrankenPHP: Docker Image and Static Binary
FrankenPHP deserves a separate chapter because it comes in two formats, and each updates differently.
Docker Image
The v1.13.2 release, from 10/21/2026, brought images with PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12, explicitly citing PHP2Shell. Two days later, v1.13.3 was released, which fixes crashes (SIGILL) of musl static binaries on older CPUs. A vulnerable PHP Docker image based on FrankenPHP is fixed the same way as official images. If your Dockerfile uses dunglas/frankenphp:1-php8.4 or 1.13-php8.4, a rebuild with --pull solves it. If it uses an older fixed tag, like 1.13.1-php8.4, edit it to 1.13.3 or higher.
Static Binary
Here lies the trap. FrankenPHP's static binary embeds PHP within the executable. Updating the operating system changes nothing. Updating the distro's php package doesn't either. The only fix is to replace the binary with a version that includes PHP 8.5.12 (v1.13.2 or later) and restart the service.
Those who package the entire application into a single executable (FrankenPHP's "embed" feature) also need to redo that build. This is exactly the case Datadog describes as "applications that bring their own PHP interpreter."
Worker Mode
In worker mode, FrankenPHP keeps the application loaded in memory between requests. CVE-2026-109619 specifically exploits state that survives from one request to the next in the same worker. There's no shortcut: update and restart all workers.
CI/CD Pipelines: How Not to Get Caught Again
PHP2Shell showed the size of the window between "patch published" and "patch running." In a poorly designed pipeline, a vulnerable PHP Docker image remains in production for weeks. In a good pipeline, it's hours.
What to Configure
- Scheduled Rebuild. A weekly job (and a manual trigger) that rebuilds the image with
--pull, even without a new commit. An updated base doesn't generate a commit, so a pipeline that only runs on push will never catch the patch. - Dependency Bot for
FROM. Renovate and Dependabot know how to open PRs updating image tags and digests. You maintain reproducibility and gain speed. - Minimum Version Gate. A step that fails the build if PHP is below the minimum fixed version:
docker run --rm "$IMAGE" php -r 'exit(version_compare(PHP_VERSION, "8.4.27", ">=") ? 0 : 1);' \
|| { echo "PHP below fixed version"; exit 1; }
- Tag by date or commit, never just
latest. This way, rollback points to a known image, and the registry shows what's in production. - Final Image Scanning, with the aforementioned caution: check the PHP version via the
PHP_VERSIONvariable, not just the package database.
What It Costs
Keeping every vulnerable PHP Docker image under control costs little. The direct cost is small: minutes of CI per week and some extra registry space. The real cost is engineering, from a few hours to a few days to standardize Dockerfiles, create the job, and test the rollback. Compare that to the cost of an unauthenticated intrusion on an exposed server.
For those running Moodle in containers, the logic is the same, with an extra caution: check the PHP version matrix for each Moodle release before switching branches. The Moodle server requirements help with this calculation.
Why Container Memory Limits (cgroup) Are Not a Complete Mitigation
This is the point I see most often confused. Since one of the flaws involves memory exhaustion, it seems logical that a --memory=512m on the container would solve it. It doesn't. A vulnerable PHP Docker image with a memory limit remains vulnerable.
What PHP Actually Recommends
The official mitigation for CVE-2026-109618 is memory_limit=-1, applied in a way that the application cannot override (php_admin_value in the FPM pool). This removes PHP's controllable ceiling. But the advisory itself admits that "an underlying allocation failure can still reach the vulnerable path."
Since memory_limit=-1 allows a script to consume all RAM, PHP suggests using a container or cgroup limit "as an availability barrier," preferably with a policy that only kills the affected worker. The key word is availability. Not security.
Four Reasons Not to Trust Cgroups
- CVE-2026-109619 has nothing to do with memory. It exploits stale state between requests. No RAM limit interferes with it.
- The limit protects the host, not PHP. When the container exceeds the cgroup, the kernel kills a process. This prevents the entire server from crashing, but it doesn't fix the corruption the attacker seeks.
RLIMIT_ASandRLIMIT_DATAmake things worse. PHP explicitly asks not to use these process limits instead of cgroups because they can precisely trigger the vulnerable allocation failure. Be careful withulimit -vin entrypoint scripts: it applies exactly anRLIMIT_AS.- Killing the Wrong Process Takes Down the Service. If the OOM killer hits the FPM master process, the entire container restarts. Repeated in a loop, this turns into a denial-of-service attack provoked by the attacker themselves.
How to Build Layered Defense While Updating
| Layer | Configuration | Role |
|---|---|---|
| PHP | memory_limit=-1 via php_admin_value |
Removes the predictable trigger for 109618 |
| PHP | session.upload_progress.enabled=0 |
Closes confirmed paths for 109619 |
| PHP 8.5 | fatal_error_backtraces=0 |
Closes the fatal backtrace path |
| Container | --memory / limits.memory in Kubernetes |
Availability barrier |
| Proxy/WAF | size limit, fields, and multipart parts | Reduces known payloads |
Datadog summarizes it correctly: these changes "reduce exposure while you update, but none fix the vulnerabilities." If you want to reinforce the edge, the guide on how to protect web servers against attacks covers WAF, fail2ban, and rate limits.
When NOT to Do It: Costly Pitfalls
In emergency fixes, the most common mistake is rushing in the wrong places. Avoid these:
- Do not use release candidate tags. Tags like
8.5.12RC1are not the final version. Use only stable tags. - Do not run
apt upgradeinside a running container. Besides not replacing the compiled PHP in the official image, the change disappears on the next restart. - Do not rely on
ini_set()or.user.inifor mitigation. PHP warns that they kick in too late, after request parsing. - Do not rebuild an 8.1 image expecting a fix. It won't come. For 7.4, 8.0, and 8.1, PHP only published minimal source code backports; the realistic solution is to migrate to 8.2+, preferably 8.3 or 8.4, as 8.2 goes out of support on 12/31/2026.
- Do not do everything directly in production. Swapping a vulnerable PHP Docker image for one from another branch without testing trades one problem for another. A branch change (8.1 to 8.4, for example) can break extensions and legacy code. Patching within the same branch (8.4.26 to 8.4.27) is low risk. Branch changes require staging/testing.
- Do not forget host hardening. The container runs on a Linux host that also needs to be up-to-date; the Linux server hardening checklist covers this layer.
And after updating, look for signs that the attack occurred before the patch. Datadog lists indicators such as memory exhaustion errors with "Unknown on line 0" in the log, segfaults (signal 11), and, most directly, a web worker opening a shell. If any of these appear, treat it as an incident, not maintenance.
How Agathas Web Solves This
If you have one or more PHP applications in containers and aren't sure that no vulnerable PHP Docker images remain in production, this is exactly the type of work we deliver with our custom software development and maintenance service.
What's Included
- Complete Inventory: running containers, registry, workers, cron jobs, and staging environments, with the actual PHP version of each.
- Rebuild and Publishing:
FROMupdate, cache-free build, push, container recreation, and validation of the running version. - FrankenPHP and Static Binaries: binary or image replacement, including in applications packaged with embedded PHP.
- Well-Applied Temporary Mitigations while branch migration is pending, at the correct level (FPM pool, not
.user.ini). - Next-Flaw-Proof Pipeline: scheduled rebuild, dependency bot, minimum version gate, and tested rollback.
- Legacy PHP Migration (7.4, 8.0, 8.1) to a supported branch, with staging/testing before production.
How It Works in Practice
We start with a brief diagnosis: you provide us with read-access to your registry and servers, and we return a list of what's vulnerable and the order of remediation. Then we execute the rebuild in an agreed-upon window, with rollback readiness. The same team handles Moodle, Linux servers, managed hosting, and integrations with the official WhatsApp API, so the entire application is under the same scrutiny, not just the container.
How to Engage Our Services
Contact us through Agathas Web's custom software development page, describe your stack (how many projects, which images, where they run), and we'll respond with a scope and timeline. For urgency related to PHP2Shell, mention it in your first message.
Conclusion: A Patch is Only Effective When It Reaches the Process
The lesson from PHP2Shell for Docker users is simple. The patch was released on 10/21, official images and FrankenPHP were updated the same day, but none of that protects a container that hasn't been rebuilt and recreated. Inventory, rebuild with --pull --no-cache, push, recreation, and version validation: that's the sequence. Container memory limits help keep the service running, but they don't close the vulnerability.
If you want to end this week certain that no vulnerable PHP Docker images remain, and with a pipeline that catches the next flaw in hours, contact Agathas Web.
Related posts

mod_php vs. PHP-FPM: Risk, Performance, and Migration
PHP identified mod_php as the highest-risk scenario in PHP2Shell. Understand architectural changes and how to migrate to FPM with zero downtime.

PHP2Shell Mitigation: Configuration Steps and Why It's Not Enough
Official PHP2Shell adjustments buy time, not security. Learn where to apply them, how to restart PHP workers correctly, and what to do before the official patch.

PHP2Shell's Wake-Up Call: Building a 24/7 Server Patch Management Routine
From the 10/17 attack to the 10/21 patch: understanding the PHP2Shell exposure window and the part you control.