From 58ffb9c2e0daf00c214cde01e753e914192f8e18 Mon Sep 17 00:00:00 2001 From: Nguyen Minh Phuc Date: Tue, 21 Jul 2026 15:11:20 +0000 Subject: [PATCH] ci: tag the trivy image so the reclaim step stops deleting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run #68 re-pulled trivy in all three matrix legs — 178MB each, ~7s each — because the reclaim step deletes it at the end of every leg: Unable to find image 'aquasec/trivy:0.72.0@sha256:cffe...' locally untagged: aquasec/trivy@sha256:cffe... Total reclaimed space: 178.6MB The `--filter until=168h` I added for exactly this reason does not work. That filter reads the image's CREATED timestamp, not the pull time, and aquasec/trivy:0.72.0 was built months ago, so it matches immediately. The comment claiming the age filter fixed this was wrong; corrected in place. Pull by digest, tag it, run the tag. A tagged image is not dangling, which is what actually takes it out of `docker image prune`'s scope. The digest is still the pin — enforced at pull time. Everything else in run #68 is clean: 76 unit (98.59% coverage), 112 integration, 18 CACHED layers per image job, zero uv hardlink warnings, and the trivy DB volume hit on 2 of 3 legs (one download, two silent). --- .gitea/workflows/ci.yaml | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) 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