Stop full rebuilds that nothing in the diff justifies #44

Merged
geusebio merged 1 commit from claude/infallible-pare-cdd36e into main 2026-08-08 14:05:32 +00:00
Owner

Scoped builds already existed, but two things kept sending runs down the build-everything path far more often than the diff warranted.

Base commit. A branch diffed against the previous push, which is wrong in both directions: a brand new branch has no previous push at all (Forgejo sends an all-zero sha) so its first run rebuilt the world, and on later pushes an image touched by an earlier commit on the branch silently stopped being built and tested. Branches now diff against the merge base with main, so a run always covers the whole branch. main keeps using the previous tip, where that is exactly right.

The matrix files. docker-bake.hcl and the three test compose files were in GLOBAL_PATHS, so editing one service's healthcheck rebuilt all ~40 images -- measured over the last 40 commits on main, the single biggest source of full rebuilds. They are machine readable, so rather than giving up they now get resolved at the base commit and at HEAD and compared target by target; only targets whose resolved definition actually moved get rebuilt.

Also narrows .github/ to .github/workflows/build.yml; mirror-baseimages.yml runs on its own schedule and has no say in this matrix.

Effect

Full rebuilds over the last 40 commits on main: 23 -> 11, and the 11 are genuinely global (the test script, the bake wrapper, changed-targets itself, build.yml). Costs ~0.2s per run.

Verified

change before after
comment-only test.yml ALL (nothing)
comment-only docker-bake.hcl ALL (nothing)
port added to one healthcheck service ALL php-84-healthchecks
arg added to the node bake target ALL node-22 node-24
service enabled in the aggregator ALL ubuntu-php-84-assertions
mirror-baseimages.yml ALL (nothing)
Node.Dockerfile node-22 node-24 unchanged
shared PHP.fragment.Dockerfile 17 php/magento targets unchanged
.github/workflows/build.yml ALL ALL
unparseable docker-bake.hcl ALL ALL

Everything fails closed: no base, an unreachable one, or a config file that will not resolve all still mean ALL. Two traps worth knowing if you touch this: TIMESTAMP defaults to today's date and is interpolated into every tag, so it is pinned while diffing; and compose reports build.context as an absolute path that differs between the base checkout and the working tree.

Registry-backed layer cache is deliberately left for a separate PR -- different risk profile (credentials, registry storage, cache poisoning).

🤖 Generated with Claude Code

Scoped builds already existed, but two things kept sending runs down the build-everything path far more often than the diff warranted. **Base commit.** A branch diffed against the previous push, which is wrong in both directions: a brand new branch has no previous push at all (Forgejo sends an all-zero sha) so its first run rebuilt the world, and on later pushes an image touched by an *earlier* commit on the branch silently stopped being built and tested. Branches now diff against the merge base with `main`, so a run always covers the whole branch. `main` keeps using the previous tip, where that is exactly right. **The matrix files.** `docker-bake.hcl` and the three test compose files were in `GLOBAL_PATHS`, so editing one service's healthcheck rebuilt all ~40 images -- measured over the last 40 commits on `main`, the single biggest source of full rebuilds. They are machine readable, so rather than giving up they now get resolved at the base commit and at HEAD and compared target by target; only targets whose resolved definition actually moved get rebuilt. Also narrows `.github/` to `.github/workflows/build.yml`; `mirror-baseimages.yml` runs on its own schedule and has no say in this matrix. ### Effect Full rebuilds over the last 40 commits on `main`: **23 -> 11**, and the 11 are genuinely global (the test script, the bake wrapper, `changed-targets` itself, `build.yml`). Costs ~0.2s per run. ### Verified | change | before | after | |---|---|---| | comment-only `test.yml` | ALL | *(nothing)* | | comment-only `docker-bake.hcl` | ALL | *(nothing)* | | port added to one healthcheck service | ALL | `php-84-healthchecks` | | arg added to the `node` bake target | ALL | `node-22 node-24` | | service enabled in the aggregator | ALL | `ubuntu-php-84-assertions` | | `mirror-baseimages.yml` | ALL | *(nothing)* | | `Node.Dockerfile` | `node-22 node-24` | unchanged | | shared `PHP.fragment.Dockerfile` | 17 php/magento targets | unchanged | | `.github/workflows/build.yml` | ALL | ALL | | unparseable `docker-bake.hcl` | ALL | ALL | Everything fails closed: no base, an unreachable one, or a config file that will not resolve all still mean `ALL`. Two traps worth knowing if you touch this: `TIMESTAMP` defaults to today's date and is interpolated into every tag, so it is pinned while diffing; and compose reports `build.context` as an absolute path that differs between the base checkout and the working tree. Registry-backed layer cache is deliberately left for a separate PR -- different risk profile (credentials, registry storage, cache poisoning). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ci: stop full rebuilds that nothing in the diff justifies
Some checks failed
Build / Run container tests (push) Failing after 2m24s
Build / Build (push) Has been skipped
dd62112461
Scoped builds already existed, but two things kept sending runs down the
build-everything path far more often than the diff warranted.

Base commit. A branch diffed against the previous push, which is wrong in
both directions: a brand new branch has no previous push at all (Forgejo
sends an all-zero sha) so its first run rebuilt the world, and on later
pushes an image touched by an *earlier* commit on the branch silently
stopped being built and tested. Branches now diff against the merge base
with main, so a run always covers the whole branch. main keeps using the
previous tip, where that is exactly right.

The matrix files. docker-bake.hcl and the three test compose files were in
GLOBAL_PATHS, so editing one service's healthcheck rebuilt all ~40 images.
Measured over the last 40 commits on main, they were the single biggest
source of full rebuilds. They are machine readable, so rather than giving
up we now resolve them at the base commit and at HEAD and compare target
by target: only targets whose resolved definition actually moved get
rebuilt. A comment-only edit to test.yml now builds nothing; adding a port
to php-84-healthchecks builds php-84-healthchecks.

Two traps that make everything look changed if you miss them: TIMESTAMP
defaults to today's date and is interpolated into every tag, so it has to
be pinned while diffing, and compose reports build.context as an absolute
path that differs between the base checkout and the working tree.

Also narrows .github/ to .github/workflows/build.yml. mirror-baseimages.yml
runs on its own schedule and has no say in this matrix.

Together that takes full rebuilds from 23 of the last 40 main commits to
11, and the 11 are genuinely global -- the test script, the bake wrapper,
changed-targets itself, build.yml.

Everything fails closed: no base, an unreachable one, or a config file
that will not resolve all still mean ALL. Getting this wrong should cost
time, never correctness.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/docker!44
No description provided.