Skip to content

ci: cache the debug PHP image instead of rebuilding it every run - #16

Merged
lisachenko merged 1 commit into
mainfrom
claude/z-engine-php-cache-1lhtkl
Aug 17, 2026
Merged

ci: cache the debug PHP image instead of rebuilding it every run#16
lisachenko merged 1 commit into
mainfrom
claude/z-engine-php-cache-1lhtkl

Conversation

@lisachenko

Copy link
Copy Markdown
Owner

The problem

tests-internal-debug is the slow job in this workflow, and the slow part is not the tests. It recompiles PHP from source inside tools/docker/php-debug.Dockerfile on every push and every pull request, once per matrix leg (8.4 and 8.5), to produce an image that is byte-identical to the one the previous run built. Nothing was cached between runs — Docker's layer cache is local to a runner, and every job gets a fresh one.

The change

The finished image is saved with docker save and stored through actions/cache@v6; on a hit it is restored with docker load and the build step is skipped entirely.

The cache key names every input that can change the image:

php-debug-image-<runner.os>-<runner.arch>-<nts|zts>-<php minor>-<hash of the Dockerfile>-<digest of php:<minor>-cli>
  • OS + architecture — a debug PHP binary is not portable across either.
  • Thread safetynts today, carried in a job-level PHP_THREAD_SAFETY env var. A ZTS variant would arrive with its own build arg and its own value, so the two flavours could never be handed the same tarball.
  • PHP minor — the matrix leg.
  • Dockerfile hash — editing the recipe invalidates the entry by itself. There is nothing to bump by hand.
  • Base image digest — resolved with docker buildx imagetools inspect --raw, which queries the registry rather than pulling layers the hit exists to avoid downloading. This is what makes a date-based rotation unnecessary: the entry expires exactly when a rebuild would produce something different, instead of rebuilding for nothing or serving a PHP patch older than the rest of the matrix runs on.

No restore-keys, deliberately. This job runs destroy_zend_class() over a copied class entry while its template is live; a prefix near-miss would run that against an image built from a recipe or a base nobody described. Either the exact build is cached or it is rebuilt.

Two guards that go with it

  • The tarball is written as image.tar.part and renamed into place. The cache is written by a post step that can still run after a step failed, and a truncated archive stored under the exact key would poison every later run — with the rename, a docker save that dies leaves no cached path at all and nothing is stored.
  • The "is this really a debug build" check now also asserts PHP_MAJOR_VERSION.PHP_MINOR_VERSION against the matrix leg. That check used to guard a build that had just happened; it now guards whatever came out of the cache, so an image restored under a key that does not describe it cannot pass quietly.

Effect

A warm run replaces a full PHP compile with a tarball download and a docker load. Entries written by a main run are readable from every PR branch, so the compile is normally paid once per Dockerfile or base-image change rather than once per pull request. A cold run does exactly what it did before, plus one registry API call and the docker save.

Notes

  • tests-internal-debug is the only job in any of the repositories reachable from this session that builds a PHP image; every other job uses shivammathur/setup-php, which is already fast. lisachenko/z-engine itself, which this Dockerfile was copied from, is outside this session's repository scope and could not be attached — the same pattern should be applied to its tools/docker/php-debug.Dockerfile job, where an NTS/ZTS matrix makes the thread-safety key component do real work.
  • AGENTS.md §6 documents the caching contract so the no-restore-keys rule is not quietly relaxed later.
  • Not verifiable from here: the build itself needs a Docker daemon. The workflow YAML parses, every run: block passes bash -n, and the imagetools --raw | sha256sum digest was confirmed against the digest Docker Hub reports for php:8.4-cli. The first CI run is the real check — it will be a cold miss on both legs, and the run after it should show the hit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KBWEmGUKhtVGSwtQ6ixuQT


Generated by Claude Code

The tests-internal-debug job recompiles PHP from source on every push and
every pull request, twice over (8.4 and 8.5), for an image whose content only
changes when the recipe or the base image does. That is minutes of runner time
per leg, spent to produce a byte-identical result.

The finished image is now stored as a `docker save` tarball through
actions/cache and loaded back on a hit. The key names everything that can
change the image - runner OS and architecture, thread safety, PHP minor, the
hash of php-debug.Dockerfile, and the digest of `php:<minor>-cli` resolved from
the registry without pulling it - so the entry expires exactly when the build
would differ, with no date rotation and no manual bump.

There are deliberately no restore-keys: this job runs destroy_zend_class()
against a live template, and a prefix near-miss would run it inside an image
built from a recipe or a base nobody described. Either the exact build is
cached or it is rebuilt.

Two guards go with it. The tarball is written under a `.part` name and renamed
into place, so a `docker save` that dies cannot leave a truncated archive for
the post step to store under the exact key. And the build-is-really-a-debug
check now also asserts the PHP minor, since it is the gate on whatever came out
of the cache rather than on a build that just happened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBWEmGUKhtVGSwtQ6ixuQT
@lisachenko lisachenko closed this Aug 17, 2026
@lisachenko lisachenko reopened this Aug 17, 2026
@lisachenko
lisachenko marked this pull request as ready for review August 17, 2026 05:13
@lisachenko
lisachenko merged commit fc74d90 into main Aug 17, 2026
32 checks passed
@lisachenko
lisachenko deleted the claude/z-engine-php-cache-1lhtkl branch August 17, 2026 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants