# CI/CD: Rebuilding PHP Images Automatically

> Rebuild Docker images automatically: scheduled builds, a version check in the pipeline and rollout by digest, so that PHP patches reliably reach production.

Source: https://phpsos.de/en/blog/php-images-in-ci-neu-bauen/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** A Docker image contains PHP as of its build time and goes stale even without code changes. If you rebuild images automatically, on a schedule and on security announcements, check the PHP version in the pipeline and roll out by digest, patches such as the one against PHP2Shell reach production in a plannable way. A green build alone is no proof; what counts is the version in the running container.

## Why images go stale without code changes

In many teams, the build pipeline starts only when someone changes code. For the application, that is logical. For security, it is a problem, because the PHP in the image comes from the base image as of the day of the last build.

PHP2Shell made this visible. The fixed versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35 appeared on 21 October 2026. A project that nobody worked on in those days got no new build and kept running with the old PHP, although the base image had long been updated. The manual way out of this situation is described in [Patching PHP in Docker containers](https://phpsos.de/en/php2shell/docker-container-patchen/). This article shows how to automate it.

## Rebuilding Docker images automatically: three triggers

A robust process combines several triggers so that none has to carry everything alone.

| Trigger | Implementation | Things to note |
|---|---|---|
| Schedule | scheduled workflow, e.g. weekly | picks up patches even without code changes |
| New base image | tool that monitors base images and proposes changes | works mainly with fixed version tags |
| Security announcement | manual start of the pipeline by the operations team | needs a clear person responsible |

With GitHub Actions, the `schedule` event handles the timetable. According to the GitHub documentation, it uses cron syntax with five fields, runs in UTC and only on the default branch. Scheduled runs can start late under high load, and in public repositories GitHub disables them after 60 days without activity. That last point hits quiet projects, precisely those that need the automatic rebuild most urgently.

For the second trigger, according to the GitHub documentation, Dependabot supports the `docker` ecosystem and proposes new states of base images as a pull request. You should know two limitations. First, someone has to review and merge the pull request, otherwise it just sits there. Second, with a branch tag such as `php:8.3-fpm`, nothing changes in the Dockerfile when the base image is rebuilt. A tool that proposes text changes then has nothing to propose. This is exactly why the scheduled rebuild is needed in addition.

The third trigger sounds banal but in practice often fails on the question of who triggers it. Decide who follows security announcements about PHP and who may start the pipeline, including at the weekend. With PHP2Shell, only one day lay between the advance notice and the patch.

## A pipeline that catches outdated PHP

The following example for GitHub Actions builds the image weekly and on demand, checks the PHP version and aborts if it is below the fixed version. Adjust registry, names and minimum version.

```yaml
name: php-image-rebuild
on:
  schedule:
    - cron: "0 3 * * 1"      # Mondays 03:00 UTC
  workflow_dispatch: {}      # manual start on security announcements

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Image ohne Cache und mit frischem Basis-Image bauen
        run: docker build --pull --no-cache -t app:${{ github.run_id }} .
      - name: PHP-Version prüfen (Mindeststand des Zweigs 8.3)
        run: |
          docker run --rm app:${{ github.run_id }} php -v
          docker run --rm app:${{ github.run_id }} \
            php -r 'exit(version_compare(PHP_VERSION, "8.3.36", ">=") ? 0 : 1);'
```

The version check works with official PHP images, whose version number indicates the actual state. If your image uses a distribution's PHP packages, the number often stays the same after a backport. In that case, check the package changelog for the CVE numbers instead of the number, as described in the article [Proving CVE numbers in the package changelog](https://phpsos.de/en/blog/cve-im-changelog-nachweisen/).

After the check, three more steps follow in practice: scan the image for known vulnerabilities, push it to the registry with date and digest, and hand the digest to the deployment. Which tags and variants are suitable as a base is explained in [Official PHP Docker images](https://phpsos.de/en/blog/offizielle-php-docker-images/).

## From build to running container

The most common error in thinking: the pipeline is green, so everything is patched. In fact, the new image is merely sitting in the registry. The patch takes effect only once every container has been recreated from the new image.

1. **Roll out by digest:** The deployment references the digest of the new image, not a moving tag.
2. **Staging first, then production:** Automated tests on a copy catch breakage caused by new base images.
3. **Verify in operation:** After the rollout, run `php -v` in the running container and compare the digest with the build.
4. **Limit the way back:** The old image remains available for a short rollback but contains the vulnerable PHP.

In Kubernetes, separate rules apply, for example on the pull policy; they are described in [Patching PHP in Kubernetes](https://phpsos.de/en/server/kubernetes/).

## Typical gaps in the chain

- **Build cache on the runner:** Without `--pull` and `--no-cache`, the old base image is reused.
- **Registry mirror:** An internal cache still serves the old state of the base image.
- **Multi-stage builds:** The PHP version of the last stage counts, but often only the first is checked.
- **Side images:** Queue workers, cron containers and internal tools have their own pipelines or none at all.
- **Hard-coded digests:** A once pinned digest of the base image is never updated if no tool monitors it.

> **Important:**
> A rebuild replaces PHP, not the data. Malicious code that got into volumes, upload folders or the database before the patch remains and must be checked separately.

## Who reacts when the rebuild fails?

An automatic rebuild is only as good as the reaction to its failure. If the version check triggers or the build aborts because of a changed base image, someone has to notice. In many teams, such messages end up in a mailbox that nobody reads, or in a channel where red builds have long been routine.

A clear rule has proven itself: a failed security rebuild is a ticket with an owner and a deadline, not one notice among many. Also record date, digest and verified PHP version for every rolled-out image. At the next announcement, this list answers in minutes which applications are affected, and it serves as proof toward auditors or insurers. How this becomes a lasting routine is described in [Patch management as a process instead of firefighting](https://phpsos.de/en/blog/patch-management-prozess/).

## How does PHP SOS help?

- **Review pipelines:** We look at your build and rollout chains and show where a patch can get stuck.
- **Automate the rebuild:** We set up scheduled builds, version checks and rollout by digest.
- **Capture all images:** We assign each running container its pipeline, including side images and legacy images.
- **Maintain it long term:** In the [security maintenance contract](https://phpsos.de/en/leistungen/security-wartungsvertrag/), we follow security announcements and trigger rebuilds.


## Frequently asked questions

**How often should a PHP image be rebuilt?**  
There is no fixed number. A regular scheduled build, for example weekly, plus an immediate build on security announcements has proven itself. What matters is that a new patch does not have to wait for the next code change.

**Is a successful build enough as proof?**  
No. A build only proves that a new image exists. The patch is effective only once the running containers have been recreated from this image and php -v inside them shows the fixed version.

**Why does the old PHP version still show up after the rebuild?**  
Common causes are a build cache that reuses the old base image, a registry mirror with an old state, or a second FROM line in a multi-stage build. Build with --pull and --no-cache and check every stage.

**Can Dependabot update base images?**  
According to the GitHub documentation, Dependabot supports the Docker ecosystem for version and security updates. It proposes changes to base images as a pull request. It does not replace a rebuild when the tag stays the same.

## Sources

- [GitHub Docs: Events that trigger workflows (schedule)](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows)
- [GitHub Docs: Ecosystems supported by Dependabot](https://docs.github.com/en/code-security/dependabot/ecosystems-supported-by-dependabot/supported-ecosystems-and-repositories)
- [Docker Hub: Official PHP image](https://hub.docker.com/_/php)
- [PHP: Supported versions](https://www.php.net/supported-versions.php)
