Safeguard
Supply Chain Security•

PHP2Shell: Rebuild PHP Docker Images and Prove Every Pod Updated

After PHP2Shell, rebuild every PHP image on 8.5.12, 8.4.27, 8.3.36 or 8.2.35, pin by digest, roll out, confirm each pod's image digest, then clean up.

Safeguard Security Team
24 min read

PHP2Shell (CVE-2026-109618 and CVE-2026-109619) lives in the PHP runtime, so every container image that ships PHP needs a rebuild on PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or on a patched distro package. Rebuild on a confirmed base, deploy by digest, check the digest on every running pod, then retire the old images.

Our PHP2Shell explainer covers the two flaws, and find and patch every PHP runtime covers hosts, panels and appliances. This post stays inside the container pipeline: image tags, digest pins, multi-stage builds, distro bases, Kubernetes rollout and registry cleanup.

What happened, from a container point of view

The PHP fix shipped on 21 October. Images carrying it arrived at different speeds depending on where your PHP comes from.

When (2026)WhatSource
20 OctPHP announces a critical release for 21 Oct, 14:00 UTC, for two flaws rated CVSS v4.0 9.2php.net
21 OctPHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 released; minimal source backports for 7.4, 8.0 and 8.1php.net
21 OctUbuntu publishes USN-8948-1 and USN-8948-2 with fixed PHP packages across its releasesUbuntu CVE tracker
21 Oct, when we checkedDebian fixes php8.4 in trixie (DSA-6574-1, 8.4.27-1~deb13u1); php8.2 in bookworm still listed as vulnerableDebian security tracker
21 Oct, when we checkeddocker-library's official image definitions list 8.5.12, 8.4.27, 8.3.36 and 8.2.35 tagsdocker-library/official-images
21 Oct, when we checkeddocker-library repo-info snapshots for php:8.3-fpm-bookworm and php:8.4-apache still reported PHP 8.3.35 and 8.4.26docker-library/repo-info
21 Oct, when we checkedAlpine v3.24 package index still listed php84 8.4.26-r0 and php85 8.5.11-r0, both built 25 SepAlpine package index

Two lessons. A fix in the definition file does not prove the image you pull contains it; images are pushed afterwards, and repo-info itself can lag. And distro packages move on each distro's schedule: on release day, apk add php84 on Alpine 3.24 had no fixed package, while Ubuntu 24.04 did.

Exploitation status is disputed, as the parent post explains, so treat any internet-facing PHP container on an old build as exposed. If you cannot rebuild a service quickly, the agencies' advice covered in governments taking websites offline over PHP2Shell applies: take it off the internet until it is rebuilt.

Where PHP comes from in your images

Sort your images by how PHP gets into them. Each source has a different fix path.

How PHP gets inExampleWhat fixes it
Official php imageFROM php:8.3-fpm-bookwormA fixed base image, then a rebuild
An image built on phpThe official WordPress image (its PHP 8.3 Apache variant builds FROM php:8.3-apache)Upstream rebuilds, then you pull and rebuild
Your internal base imageFROM registry.example.com/base/php:8.3Rebuild the base, then every app on it
Copied from another stageCOPY --from=php:8.3-cli /usr/local/bin/php ...A rebuild that resolves the stage to a fixed PHP
Distro packagesapt-get install php8.3-fpm, apk add php84The distro's patched package, rebuilt without cache
Statically linked serverOne binary with PHP compiled inA new release of that binary

php.net lists PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed as affected, with mod_php at highest risk. Patch CLI-only images too; they come from the same source tree.

How the official php image is built and tagged

The Docker Hub php image does not use distro PHP packages. Each Dockerfile downloads the php.net source tarball, checks its SHA-256 and GPG signature, and compiles it. The 8.4 Alpine FPM Dockerfile we fetched sets ENV PHP_VERSION 8.4.27, starts FROM alpine:3.24 and installs under /usr/local. The README adds that the image blocks Debian's own PHP packages. So apt-get upgrade or apk upgrade inside a FROM php:... image never changes PHP. Only a new base image does.

Variants

VariantWhat it containsPHP2Shell noteExample tag
cliPHP CLI; per the README, the only variant with php-cgiphp-cgi is a listed affected SAPIphp:8.3.36-cli-bookworm
apacheDebian's Apache with mod_php, mpm_prefork by defaultmod_php is highest riskphp:8.3.36-apache-trixie
fpmPHP-FPM behind a reverse proxyAffected; exploitability depends on configurationphp:8.3.36-fpm-alpine3.24
ztsThread-safe buildSame fixphp:8.3.36-zts-trixie

Variants come on Debian trixie and bookworm and Alpine 3.24 and 3.23, with no Apache variant on Alpine. There are no 8.1, 8.0 or 7.4 tags, so FROM php:8.1-fpm will never receive this fix from Docker Hub.

What the short tags point to

The tag name tells you how much can change underneath you. From the definition file on 21 October:

TagResolved toWhat can change later
php:latest, php:cli, php:fpm, php:apache8.5.12 on trixieMinor version, patch version, Debian release
php:8.3-fpm8.3.36 FPM on trixiePatch version, Debian release
php:8.3-fpm-bookworm8.3.36 FPM on bookwormPatch version, base layer updates
php:8.3.36-fpm-bookworm8.3.36 FPM on bookwormBase layer updates
php:8.3.36-fpm-bookworm@sha256:...One exact imageNothing

The README advises pinning the Debian suite if you install extra packages. And even full version tags move: repo-info showed php:8.3-fpm-bookworm rebuilt on 20 and 21 October while still on 8.3.35, so tags are rebuilt when PHP does not change. Only a digest is fixed.

Floating tags versus digest pinning

Both approaches fail on a day like 21 October, just in different directions.

A floating tag such as php:8.3-fpm picks up the fix on your next build that pulls a fresh base. But your cache may hold the old base unless you pass --pull, running containers do not change until you redeploy, and you cannot easily say which PHP an image contains.

A pinned digest never changes. Docker's build best practices put it plainly: by pinning, "you're opting out of automated security fixes". Docker suggests an updater such as Dependabot to keep pins moving.

What works under pressure is both: the full tag for humans, the digest for machines, and a bot or runbook that bumps both.

# The tag documents intent; the digest decides what is pulled
FROM php:8.3.36-fpm-bookworm@sha256:<index digest>

Kubernetes documents the same rule for image references: when a tag and digest are both given, only the digest is used to pull.

Walkthrough: move a digest-pinned service to a fixed PHP

  1. Find every PHP base in your repositories, including Compose files and multi-stage copies.
grep -rnE --include='Dockerfile*' --include='*.dockerfile' '^\s*FROM\s+\S*(php|wordpress)' .
grep -rnE --include='*compose*.y*ml' 'image:\s*\S*(php|wordpress)' .
grep -rnE --include='Dockerfile*' 'COPY\s+--from=\S*php' .
  1. Pick a target tag on the same minor and suite. 8.3 to 8.3.36 is a patch bump; jumping to 8.5 is a separate project.

  2. Confirm the tag really contains the fix, not just the web page or definitions file.

docker pull php:8.3.36-fpm-bookworm
docker run --rm php:8.3.36-fpm-bookworm php -r 'echo PHP_VERSION, PHP_EOL;'
docker run --rm php:8.3.36-fpm-bookworm php-fpm -v

For the Apache variant, also confirm the module is the one you expect: docker run --rm php:8.3.36-apache-bookworm apache2ctl -M | grep -i php.

  1. Resolve the digest of the multi-platform index, not of one platform's manifest, so arm64 and amd64 nodes get the same fix.
docker buildx imagetools inspect php:8.3.36-fpm-bookworm      # read the top "Digest:" line
crane digest php:8.3.36-fpm-bookworm                          # same value, one line
  1. Update the FROM line with tag and digest and put the CVE IDs in the commit message.

  2. Rebuild without stale layers. --pull fetches a fresh base and --no-cache stops apt-get or apk steps reusing old layers.

TAG=$(git rev-parse --short HEAD)
docker build --pull --no-cache -t registry.example.com/shop/web:$TAG .
  1. Test the built image, not the base.
docker run --rm --entrypoint php registry.example.com/shop/web:$TAG -v
docker run --rm --entrypoint php registry.example.com/shop/web:$TAG -m    # extensions still load?
  1. Push under a new, unique tag and record the digest you will deploy.
docker push registry.example.com/shop/web:$TAG
crane digest registry.example.com/shop/web:$TAG

Extensions built with docker-php-ext-install or pecl install recompile against the new base during the rebuild. The README recommends pinning explicit PECL versions and installing them in separate commands so failures are not skipped. Do not copy compiled .so files from an old image.

Multi-stage builds that copy a PHP binary

Some teams avoid the official image as a runtime base and lift PHP out of it in a build stage. A typical pattern:

FROM php:8.3-cli-bookworm AS phpbin

FROM debian:bookworm-slim
COPY --from=phpbin /usr/local/bin/php /usr/local/bin/php
COPY --from=phpbin /usr/local/lib/php /usr/local/lib/php
COPY --from=phpbin /usr/local/etc/php /usr/local/etc/php

The final image contains whatever PHP the phpbin stage resolved to on build day. The final FROM says only Debian, the copied binary has no dpkg or apk record so package-based tools will not list it, and a cached stage keeps the old PHP even after the tag moved. Rebuild with --pull --no-cache or bump a digest pin, then check the image directly:

IMG=registry.example.com/shop/worker:prod
docker run --rm --entrypoint sh "$IMG" -c '
  for b in php php-fpm php-cgi; do
    p=$(command -v "$b") && echo "$p: $("$p" -v | head -1)"
  done
  dpkg -S "$(command -v php)" 2>/dev/null || echo "php is not owned by any package"'

The Composer image is a related case. Its Dockerfile builds FROM php:8-alpine, and its README says "You should not rely on the PHP version in our container." Copying only /usr/bin/composer from it, as the README recommends, brings no PHP runtime. Running your app on the Composer image does. Keep Composer in a build stage.

Distro-packaged PHP in other base images

If your image starts from a plain distro and installs PHP packages, the fix comes from that distro, often with a version number that looks unpatched.

BasePackageFixed version (21 Oct, when we checked)Source
Ubuntu 22.04php8.18.1.2-1ubuntu2.28Ubuntu
Ubuntu 24.04php8.38.3.6-0ubuntu0.24.04.13Ubuntu
Ubuntu 26.04php8.58.5.4-0ubuntu1.5Ubuntu
Debian 13 trixiephp8.48.4.27-1~deb13u1 (DSA-6574-1)Debian
Debian 12 bookwormphp8.2Not yet fixed; listed vulnerableDebian
Alpine 3.24php84, php85Not yet updated; 8.4.26-r0 and 8.5.11-r0 listedAlpine
RHEL and UBIphpRed Hat rates CVE-2026-109618 Important, preliminary CVSS 3.1 8.1; no fix state listed on the CVE pageRed Hat

Ubuntu 24.04's fixed package still reads 8.3.6 upstream. That is a backport, which is why the changelog is the test, not php -v. Red Hat notes that standard RHEL deployments use PHP-FPM rather than mod_php, which it says makes code execution harder in practice, but still calls the fixed release the only complete fix.

Check inside the built image, not on the build host:

# Debian and Ubuntu based
docker run --rm --entrypoint sh IMG -c "dpkg-query -W 'php*' 'libapache2-mod-php*' 2>/dev/null"
docker run --rm --entrypoint sh IMG -c "zcat /usr/share/doc/php8.3-common/changelog.Debian.gz | grep -m1 CVE-2026-109618"

# Alpine based
docker run --rm --entrypoint sh IMG -c "apk info -v 2>/dev/null | grep -E '^php[0-9]+-'"

# RHEL, UBI and Fedora based
docker run --rm --entrypoint sh IMG -c "rpm -q --changelog php-common | grep -m1 CVE-2026-109618"

Slim images sometimes strip /usr/share/doc; then compare the package version with the distro tracker. If the distro has no fix yet, switch that service to the official php image on a fixed tag, build from the php.net release, or keep it off the internet until the package lands. Do not run two PHP installations in one image; the official README warns against it.

Rolling out on Kubernetes and proving every pod changed

A rebuilt image in a registry protects nothing until every pod that serves PHP runs it.

Why re-pushing the same tag often does nothing

Kubernetes sets imagePullPolicy when an object is created. If you omit it and use a tag other than :latest, it becomes IfNotPresent, which per the docs pulls the image "only if it is not already present locally". Re-push shop/web:prod with a fixed PHP, restart the pods, and any node with shop/web:prod cached starts the old image again.

Deploy by a new tag or, better, by digest:

NEW=sha256:<digest from step 8>
kubectl -n shop set image deployment/web php=registry.example.com/shop/web@$NEW
kubectl -n shop rollout status deployment/web --timeout=15m

kubectl rollout status exits with zero on success and non-zero if the Deployment passes its progress deadline (600 seconds by default), so it works as a pipeline gate. With Helm or GitOps, change the digest in values or manifests instead, or the next sync puts the old image back.

Do not forget the other workload types

  • StatefulSets with an OnDelete strategy wait for you to delete each pod.
  • CronJobs pick up the template on their next run; a running Job keeps its old image.
  • Init containers and sidecars that ship PHP need the same update.
  • kubectl rollout restart does not change the image reference, so it helps only if the reference already points at the fixed image.

Prove it with image digests

Every running container reports the image it actually started from in status.containerStatuses[].imageID. List any pod that is not on the new digest:

NEW=sha256:<digest from step 8>
kubectl -n shop get pods -l app=web \
  -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[?(@.name=="php")].imageID}{"\n"}{end}' \
  | grep -v "$NEW"

Empty output means every matching pod runs the fixed image. For a cluster-wide list, including init containers:

kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .status.initContainerStatuses[*]}{.imageID}{" "}{end}{range .status.containerStatuses[*]}{.imageID}{" "}{end}{"\n"}{end}' \
  > running-images.tsv

Compare it with the digests you know contain a fixed PHP, then spot-check one pod per workload:

kubectl -n shop exec deploy/web -c php -- php -r 'echo PHP_VERSION, PHP_EOL;'

The rollback trap

Deployments keep ten old ReplicaSets by default, each still pointing at a vulnerable image. A routine kubectl rollout undo or Helm rollback puts PHP2Shell back into production. Check kubectl -n shop get rs -o wide and tell on-call staff that rolling back past today means rolling back the fix.

Registry cleanup

Old images can come back through a rollback, a forgotten staging cluster or someone pulling last month's :prod. Clean up after the rollout is verified, never before: deleting a digest that running pods reference can stop them rescheduling onto nodes without the image cached.

  1. Inventory tags and the PHP inside each. Images built on the official php image inherit its PHP_VERSION variable, readable without a pull.
REPO=registry.example.com/shop/web
for t in $(crane ls "$REPO"); do
  v=$(crane config "$REPO:$t" | jq -r '.config.Env[]? | select(startswith("PHP_VERSION="))')
  printf '%s\t%s\n' "$t" "${v:-no PHP_VERSION env, check by running php -v}"
done
  1. Move every floating internal tag (prod, stable, your internal base/php:8.3) to a fixed digest. Rebuild internal base images first, then the apps on them.

  2. Keep evidence. If you see any sign of compromise, keep the exact image and digest that ran.

  3. Delete or expire the rest. crane delete "$REPO@sha256:..." removes a manifest; self-hosted registries usually need garbage collection afterwards. On Amazon ECR, lifecycle policies expire images by age, count or tag pattern; AWS recommends running the preview first and says expiry happens within 24 hours.

  4. Stop the next old build with a CI check that fails when an image's PHP is below the fixed release for its branch.

What would have helped

Practices any team can use

  • One owned base image per PHP line, with an automated rebuild cascade to the apps on it.
  • Tag plus digest pins with an automated bumper, so you get reproducible builds and a pull request within hours of a new base.
  • Weekly rebuilds with --pull --no-cache, so an emergency bump is small.
  • Deploy by digest and keep a daily running-image report, so incident day starts with a list, not a guess.
  • No unpinned PHP copied through COPY --from.
  • Supported branches only. No official 8.1 tags exist; 8.2 reaches end of life on 31 December 2026.

Zero-Day Discovery and Gold

Two Safeguard capabilities support the "start clean, rebuild fast" habit. To be precise about what they did and did not do here:

Zero-Day Discovery looks for previously unknown flaws before a CVE exists, in an agentic mode and a deterministic scanner mode, and publishes findings as SGZ advisories before they reach public CVE databases. It covers seven package ecosystems. PHP core is a language runtime outside those ecosystems.

Gold Open Source at gold.safeguard.sh is free and needs no login: searchable CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version, a free read API and a CI gate GitHub Action.

Gold Registry at registry.safeguard.sh is paid. It supplies hardened drop-in components and container images held to zero exploitable critical or high CVEs, built to SLSA L3 or higher, signed with Sigstore, with an evidence page per artifact; an artifact loses its badge if it regresses. We are not claiming a patched PHP image was in Gold before the public fix. The point is the habit: pull bases from a curated, signed source, verify them, and keep a pipeline that can rebuild everything fast.

Where Safeguard fits

Safeguard would not have prevented PHP2Shell, and it does not roll out your pods or delete registry images for you.

  • See what your images contain. safeguard scan --image <name> builds an SBOM of an image, including OS packages, and matches it against vulnerability databases, with JSON results under ./output; --dir . covers repositories and --sbom <file> vendor SBOMs. Confirm the scan reports PHP where it was compiled from source or copied between stages; if not, the php -v checks above are the source of truth.
  • Find every project with a given component and version in the platform, to build the rebuild list.
  • Gate the next build. Policies and gates evaluated in the platform can stop an image with a vulnerable PHP package from being promoted.
  • Make the change with approval. The Safeguard CLI's assistant can edit a FROM line, run the build and the kubectl checks above, with your approval according to the /permissions mode you pick (Ask, Accept edits, Plan, Auto, Strict). safeguard exec runs one non-interactive turn for CI. Autonomous remediation can open fix PRs for application dependencies; it is off by default.

Where it does not help: managed platforms or hosts you do not scan, knowing whether a pod restarted, or lowering priority through reachability, since the bug sits in the runtime rather than your code. Database matching can lag a same-day advisory and distro backports make version matching noisy, so confirm against your vendor.

Download Safeguard at https://safeguard.sh/download and read the CLI docs at https://docs.safeguard.sh/docs/cli .

Common mistakes and troubleshooting

  • Pods still report the old PHP. The same tag was re-pushed and nodes had it cached under IfNotPresent. Deploy by digest.
  • The rebuild still has the old PHP. The base was cached, or the digest pin was never bumped.
  • The scanner says the image is clean. The binary may not be owned by any package. Run php -v inside it.
  • A PECL extension stopped building. Pin the PECL version for your PHP line and check logs for skipped failures.
  • The image moved to trixie by itself. Short tags such as php:8.3-fpm follow the newest Debian suite. Pin the suite.
  • Compose still runs the old container. Run docker compose pull before docker compose up -d.
  • A rollback brought the bug back. Old ReplicaSets and Helm revisions point at vulnerable images.

For the other incidents of October 2026, see our October 2026 incident roundup.

FAQ

Which official php image tags contain the PHP2Shell fix?

Tags for 8.5.12, 8.4.27, 8.3.36 and 8.2.35, in every variant and suite. Confirm with php -v in the pulled image, because published images can lag the definition file.

Should I pin the base image by digest or use a floating tag?

Both: the full tag plus digest, with an automated bump. A floating tag hides what you run; a digest without an updater keeps you on the vulnerable build.

Does apt-get upgrade in my Dockerfile fix PHP2Shell?

Not on the official php image, which compiles PHP from source. It helps on images that install distro PHP packages, once the distro ships a fix and you rebuild without cache.

Our Dockerfile uses php:8.1-fpm. What now?

There are no maintained 8.1 tags. Move to 8.3 or newer (8.2 ends on 31 December 2026), or use a vendor package that backports the fix.

How do I prove every Kubernetes pod runs the new image?

Deploy by digest, wait for kubectl rollout status to exit with zero, then list each container's imageID and filter out the new digest. Empty output means done.

Should we delete old vulnerable images from the registry?

Yes, after the rollout is verified and any image needed for investigation is kept. Move floating tags first, then expire old digests.

We use the official WordPress image. Is that covered by updating PHP?

Its PHP 8.3 Apache variant builds on php:8.3-apache, so it gets the fix only when it is rebuilt on the fixed base. Pull the new tag, confirm php -v inside it, and redeploy. Updating WordPress core does not change PHP.

Fix it without waiting for upstream

In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would have helped

These are recordings of real sessions. None shows PHP2Shell; each shows a flow you would reuse for it.

The first demo shows the Safeguard CLI fixing a lodash CVE: it reads the finding, asks before each command, edits package.json, runs install and tests with approval and pushes nothing. For PHP2Shell, you would run safeguard scan --image against your PHP image and ask the assistant to bump the FROM tag and digest, rebuild and run php -v; it asks before each command the same way.

Click through it at your own pace, or open it full screen.

The second demo is a desktop app session: the CLI session shows in the conversation list, you ask for work on your computer, pick the folder, and approve or deny each command and file edit, ending with an upgrade done and tests passing. Here the folder would hold your Dockerfile, and the commands you approve would be the build, the image test and the kubectl digest check.

Click through it at your own pace, or open it full screen.

The third demo is a real Android session that sends work to a computer running the CLI and approves the command on the phone with Allow once, Allow for turn or Deny. On a day like 21 October, an on-call engineer away from a desk could approve the rebuild and rollout steps one at a time.

Click through it at your own pace, or open it full screen.

Quick check

You re-pushed shop/web:prod with PHP 8.3.36 and ran kubectl rollout restart. Why might pods still run 8.3.35?

With a non-latest tag, Kubernetes defaults to imagePullPolicy: IfNotPresent, so nodes that already cached shop/web:prod start the old image. Deploy by the new digest and check each pod's imageID.

Your image is FROM debian:bookworm-slim and copies /usr/local/bin/php from a php:8.3-cli stage. How do you get the fix?

Rebuild with --pull --no-cache, or bump the stage's digest pin, so the stage resolves to a PHP 8.3.36 base. Then run php -v in the final image, because no package database records the copied binary.

Sources

Last reviewed: 21 October 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.