docker: remove images no container or declaration uses, keeping the previous of each line (hq ADR 0251)

Images pulled by digest are not dangling, so the weekly prune never takes an old version
and every machine keeps every image it ever ran. docker_prune_images takes them, keeps what
the declaration names (asked of the controller; no answer, nothing removed) and the one before,
and is a dry run unless asked with a why.
This commit is contained in:
jochen
2026-10-08 11:56:36 +02:00
parent f7b1853b8b
commit 2665693bb3
7 changed files with 737 additions and 1 deletions
+25
View File
@@ -135,6 +135,7 @@ A failure is an error naming how it failed, never an empty answer.
| `docker_top` | r | the processes inside one container |
| `docker_images` | r | images, largest first, with the containers using each; `dangling`, `unused` or `used` |
| `docker_prune` | a | dangling images and build cache, and stopped containers the mesh does not hold if `containers` is true. **A dry run unless `dry_run` is false. Never a volume** |
| `docker_prune_images` | a | named images no container and no declaration uses, keeping the previous version of each line (below). **A dry run unless `dry_run` is false, which needs `why`. Never forced** |
| `docker_disk_usage` | r | `docker system df -v`: total, active and reclaimable per kind, with the largest of each |
| `docker_networks` | r | networks, subnets, and the containers on each |
| `docker_volumes` | r | volumes, who mounts each, whether the mesh holds one of them, anonymous or not, and sizes if asked |
@@ -198,6 +199,29 @@ transcript that already copied it.
`docker_inspect` shows a container's own command line (`Path`/`Args`, `Cmd`, `Entrypoint`) redacted
the same way.
## Removing the images nothing uses (hq ADR 0251 §5)
The weekly prune takes dangling images only, and an image pulled by digest is not dangling, so every
version of every module a machine ever ran stays on it. `docker_prune_images` takes those, and keeps:
- every image a container on the machine uses, in any state;
- every image the machine's declaration names — what the mesh would send it now and what it was last
sent — asked of the controller's `images` verb (the manifest's `invokes`). **No answer, an error, or
no machine name (`MESH_NODE`) means nothing is removed**: every image is answered as kept,
`declaration-unknown`;
- every image younger than `older_than_days` (seven by default, the weekly prune's week);
- in each **line** holding an image kept for one of the first two reasons, the newest other image: the
previous version, so going back needs no pull. A line is the images sharing a repository name,
joined across names when one image carries several (a build's local tag and the store's name).
A declared reference is matched against the runtime's own names for an image (`docker.io/` and
`library/` left out, a bare name tagged `latest`), by the whole reference and then by its digest.
Dangling images are not taken here; they stay the weekly prune's. A real run removes each image by
every name it carries, one image at a time and never with force, so the runtime refuses an image a
container uses; a refusal is reported for that image and the rest go on. The bytes it states are each
image's size summed, an upper bound, because images share layers.
## Tests
```
@@ -213,6 +237,7 @@ The tests run against a fake runner and cover:
- the environment left out of `inspect`;
- the restore note on a mesh-held act;
- prune being a dry run by default and never reaching a volume, a mesh container or `--volumes`;
- image pruning: each reason an image is kept, a two-name image being one line, the previous being the newest other image, nothing removed without the controller's answer or in a dry run, removal never forced, a real run refused without `why`;
- the log merge;
- a printed secret found by name and never answered by value, in the scan and in `docker_logs`;
- size parsing;