Containers make deployment easier, but they also hide the software inside them, including runtimes such as PHP.
This is the second of three articles in the series Lessons from PHP2Shell, which uses the critical PHP flaws disclosed on 21 October 2026 as a case study in how organisations track and patch shared software. Part 1 covered servers that an organisation manages directly, along with the background to PHP2Shell and the patching timescales expected in the UK. This part looks at containers and other places where software is bundled out of view, and at the developer machines that often sit outside normal patching altogether. Part 3 covers appliances and hosted services, where the fix depends on a vendor.
Why containers hide software
A container image is a packaged bundle holding an application, its runtime and the operating system files they need. The software inside is fixed at the moment the image is built, and it stays that way until someone rebuilds the image. This is what makes containers predictable, but it also means that updates applied elsewhere do not reach them.
WordPress illustrates the point well. In the official WordPress image, the WordPress files are kept on a separate storage volume, so updating WordPress from its dashboard works as expected and the dashboard reports that everything is current. The PHP runtime, however, lives in the image itself and is untouched by those updates. A site could therefore show as fully up to date throughout the PHP2Shell incident while still running a vulnerable version of PHP.
Keeping an inventory of containers
A server inventory, as described in Part 1, lists the packages installed on each machine, but it will not normally see inside the containers running on it. Containers need an inventory of their own, built at two levels.
The first level is the images an organisation builds. These can be recorded at build time by generating a software bill of materials, or SBOM, which lists every component in the image along with its version. Tools such as Syft and Docker Scout produce SBOMs automatically, and storing them alongside each image means that a question like “which of our images contain PHP 8.4.26?” can be answered by a search rather than an investigation. Scanners such as Trivy and Grype can also check images held in a registry against known vulnerabilities on a schedule.
The second level is what is actually running. Images in a registry are only half the picture, since a host may still be running a container built from an image that has since been replaced. Listing running containers across every host, with docker ps, and recording each container’s image and build date, with docker image inspect, closes that gap. Labelling each container with an owner at deployment makes the resulting list far more useful.
Forgotten containers and developer machines
The hardest containers to inventory are those that were never recorded. A familiar pattern is that someone needs a tool quickly, such as a wiki or a dashboard, finds a single docker run command in a project’s documentation and runs it. The tool works, so it stays, often for years, with no Dockerfile in a repository and no named owner. This is a modern form of shadow IT, and it is easy to overlook because it usually runs on the organisation’s own servers. A sweep of every host running Docker or Podman, including test machines and older virtual machines, is the only reliable way to find these containers, after which each should either be given an owner or removed.
Developer laptops are a related blind spot. Local development tools such as XAMPP, MAMP and Laragon install their own copy of PHP, separate from any server, and none had issued an advisory a few days after disclosure. Static builds, which package a runtime into a single program, raise the same issue: FrankenPHP’s standalone builds embed PHP, and each needed a rebuild by its maker before users could update. Endpoint management tools can usually report installed applications on staff machines, and including development tools in that reporting brings them within the inventory.
Patching containers quickly
The upstream part of the response to PHP2Shell was quick. The official PHP images on Docker Hub were rebuilt for every supported branch within about two hours of the PHP release. Those rebuilt images have no effect, though, until each organisation rebuilds its own images and replaces its running containers, because pulling a new image does not change a container that is already running. For a typical Docker Compose setup, the steps are:
docker compose build --pull --no-cache
docker compose up -d
The --pull option fetches the updated base image, and --no-cache stops Docker reusing an older layer that still contains the vulnerable runtime.
Doing this by hand for every image does not scale, and it is unlikely to meet the NCSC’s five-day target for internet-facing systems discussed in Part 1. Two forms of automation help. Dependency tools such as Renovate or Dependabot can watch base images and open a change request when a new version is published, so that updates are proposed without anyone having to notice them. A scheduled rebuild in the build pipeline, for example weekly, also ensures that security fixes in base images reach production even when the application itself has not changed.
Images built on old runtime versions need separate attention. A Dockerfile beginning FROM php:7.4-fpm will never receive an official fix, since PHP 7.4 is no longer supported. Community images offering patched builds of older PHP versions appeared within days, but adopting one means trusting an unfamiliar third party with a production runtime, and is better treated as a supply chain decision than a quick fix.
Proving the patch worked
The Dockerfile records what was requested, not what is running, so verification should be done against the live container, for example with docker compose exec <service> php -v. Rescanning the running images with the same tool used for the inventory gives a second check and a record for audit purposes.
Containers have one advantage when it comes to checking for compromise. A container rebuilt from a fresh image starts clean, so anything an attacker placed inside the old one is discarded. The exception is data on mounted volumes, such as the folder where WordPress stores its plugins and uploads, which survives every rebuild. Any internet-facing container that was unpatched after 17 October, when exploitation attempts began, should have its volumes checked for web shells and other unexpected files, as described in Part 1.
Containers did not cause the PHP2Shell problem, and in some respects they made the response easier, since a rebuilt image replaces the vulnerable runtime cleanly. Their weakness is visibility. An organisation that records an SBOM for every image it builds, keeps a list of what is running and who owns it, and rebuilds images automatically will be able to respond to the next flaw in a shared component within days rather than weeks. Part 3 turns to the software an organisation cannot rebuild at all, inside appliances and hosted services.
At Evolve North, we help organisations gain visibility of their technology estate, identify hidden risks and implement practical measures to strengthen cyber resilience. If you’d like a clearer understanding of the software supply chain risks within your organisation, contact us at info@evolvenorth.com or call 01748 905 002 to discuss how we can help.
