diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 6322158..4e1077f 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -431,11 +431,30 @@ jobs: # comment here claimed, so the retention story matters. Trivy replaces the DB in # place rather than accumulating versions, so the volume stays at roughly one DB, # and the weekly prune CronJob reclaims it at the cost of one re-download. + env: + TRIVY: aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f run: | + # Pull by digest, then give it a local tag, and run the TAG. + # + # An image pulled by digest carries no tag, which makes it dangling the moment its + # container exits — so the reclaim step below deleted it at the end of every matrix + # leg and the next leg paid a 178MB re-pull. Three legs, three pulls, every run. + # + # The `until=168h` filter there does not save it. That filter reads the image's + # CREATED timestamp, not when it was pulled, and this image was built months ago, + # so it matches the age filter immediately. A tag is what actually takes an image + # out of `docker image prune`'s scope. Verified in run #68: "Unable to find image + # ... locally" in all three legs, and "untagged: aquasec/trivy@sha256:cffe..." in + # each prune. + # + # The digest is still the pin — it is enforced here, at pull time. `trivy:pinned` + # is a local alias for an image whose content was already verified. + docker pull "${TRIVY}" + docker tag "${TRIVY}" trivy:pinned docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v svcforge-trivy-db:/root/.cache/trivy \ - aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f \ + trivy:pinned \ image \ --severity HIGH,CRITICAL \ --ignore-unfixed \ @@ -482,12 +501,17 @@ jobs: # # Named volumes are never pruned here: that is where the trivy vuln DB lives. # - # Both prunes carry an age filter, because "dangling" catches more than it looks. - # An image pulled by digest has no tag, so it is dangling the moment its container - # exits. A bare `docker image prune -f` therefore deleted the trivy image on every - # run and bought back a 157MB pull on the next one. The act runner image survived - # that only by accident: this step executes inside an act container, so the image - # is in use exactly while the prune runs. Relying on that is not a design. + # "Dangling" catches more than it looks: an image pulled by digest has no tag, so it + # is dangling as soon as its container exits. That is why the trivy scan step tags + # its image — an age filter does NOT protect it, because `until` reads the image's + # created time, and a released tool image is always older than any useful window. + # Run #68 proved that: trivy was untagged and deleted in all three legs despite + # `until=168h`, and re-pulled 178MB each time. + # + # The age filter stays anyway, for the buildx cache, which really does grow by age. + # The act runner image survives only because this step runs inside an act container, + # so the image is in use exactly while the prune runs. That is luck, not design; if + # it ever starts disappearing, tag it the same way. if: always() run: | docker image prune -f --filter until=168h