ci: cache the debug PHP image instead of rebuilding it every run - #16
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
tests-internal-debugis the slow job in this workflow, and the slow part is not the tests. It recompiles PHP from source insidetools/docker/php-debug.Dockerfileon 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 saveand stored throughactions/cache@v6; on a hit it is restored withdocker loadand the build step is skipped entirely.The cache key names every input that can change the image:
ntstoday, carried in a job-levelPHP_THREAD_SAFETYenv 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.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 runsdestroy_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
image.tar.partand 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, adocker savethat dies leaves no cached path at all and nothing is stored.PHP_MAJOR_VERSION.PHP_MINOR_VERSIONagainst 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 amainrun 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 thedocker save.Notes
tests-internal-debugis the only job in any of the repositories reachable from this session that builds a PHP image; every other job usesshivammathur/setup-php, which is already fast.lisachenko/z-engineitself, 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 itstools/docker/php-debug.Dockerfilejob, 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-keysrule is not quietly relaxed later.run:block passesbash -n, and theimagetools --raw | sha256sumdigest was confirmed against the digest Docker Hub reports forphp: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