Skip to content

feat(pruner): keep images and containers of other workloads - #40

Merged
azlekov merged 3 commits into
mainfrom
feat/pruner-keep-images-containers
Nov 5, 2026
Merged

azlekov merged 3 commits into
mainfrom
feat/pruner-keep-images-containers

Conversation

@azlekov

@azlekov azlekov commented Nov 5, 2026 •

Copy link
Copy Markdown

Closes #39

What changes

  • DOCKER_PRUNE_KEEP_LABELS keeps images and stopped containers carrying any listed key or key=value, plus the image a kept container uses.
  • DOCKER_PRUNE_KEEP_IMAGES keeps images by name pattern (variscite:* ghcr.io/acme/*), for images you cannot label. * crosses /, and a bare repository covers all its tags.
  • Protection holds in the normal and the low-space pass. Each full pass logs every kept image with its size, and every kept container.
  • With both settings empty, the pruner makes the same docker system prune call as today.
  • PRUNE_FREE_PCT now defaults to 5 instead of 15 (separate commit).

Please review: two departures from the issue

1. Labels do not map to label!= filters. The issue asks for that, but it silently loses protection once two labels are set. Given several label!= filters, the daemon spares only what carries all of them. Measured on Docker 29.9.3:

Carries one of two keep labels containerd store overlay2
Image kept deleted
Stopped container deleted deleted

So with a keep setting active, the pruner asks docker about one label at a time and removes the rest itself, without -f. The reference patterns needed this path anyway. Everything is listed before anything is removed, and if any listing fails, the pass stops rather than treating "no list" as "nothing to keep". Networks and build cache are still pruned with the same age limit as docker system prune.

Image age comes from the daemon's own docker image ls --filter until= list, because the stores count differently: overlay2 from the build, containerd from arrival on the host. Measured with an image built long ago and pulled just now: on containerd the real prune keeps it, while a check on .Created would have removed it.

2. The threshold change was requested separately. The issue lists thresholds as out of scope, but the 5% default was asked for alongside this work. It is its own commit, so it can be reverted alone. With 5%, the low-space pass starts much later, and the free-space check still runs only hourly, so a fast-filling disk has less headroom.

Known limits

  • The keep settings need Docker 25 or later on the host: Docker 23 and 24 reject until on image ls, and 25 to 29 accept it. On an older daemon the pass logs this and skips the docker prune, so images are not reclaimed there while a keep setting is on. With both settings empty there is no such requirement.
  • Networks are not protected: a kept stopped container on a custom network can lose it, so it needs docker compose up rather than docker start. This is noted in the README.
  • A reference pattern protects the image, not its stopped containers. Only a keep label protects those.

Testing

  • New tests/pruner-docker.sh runs against throwaway docker:29-dind daemons, one per image store, and stops if it finds itself connected to any other daemon. It adds a docker:dind pull and two privileged containers to the Shell scripts CI job, and skips when no docker daemon is available.
  • It passes locally on both stores. Swapping in the issue's literal label!= mapping made it fail on both, and computing image age from .Created makes it fail on containerd.
  • The rest of tests/ passes in ubuntu:24.04, and shellcheck -x is clean on CI's file list.
  • docker compose config shows a quoted .env value reaching the pruner unquoted, with * intact.
  • Not tested on a real host.

After merge

Beelink needs Docker 25+, DOCKER_PRUNE_KEEP_IMAGES=variscite:* in its .env and the new image rolled out. Until then the Variscite image is still exposed to pruning. Any host that sets PRUNE_FREE_PCT in .env keeps its own value.

🤖 Generated with Claude Code

azlekov and others added 2 commits November 5, 2026 13:57
DOCKER_PRUNE_KEEP_LABELS and DOCKER_PRUNE_KEEP_IMAGES protect images and stopped containers from
the docker prune, in the normal and the low-space pass. Each full pass logs what it kept, with sizes.

With either set, the pruner selects what to remove itself instead of calling docker system prune:
that call has no reference filter, and with several label!= filters it spares only what carries all
of them (containers on every image store, images on overlay2). Both empty keeps today's exact call.

tests/pruner-docker.sh runs this on throwaway docker:dind daemons, one per image store.

Closes #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PRUNE_FREE_PCT now defaults to 5 instead of 15, so the hourly low-space pass with its 2h age limit
starts only when a disk is nearly full. Hosts that set PRUNE_FREE_PCT in .env are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Image age now comes from docker image ls --filter until=, not .Created: containerd counts from arrival
on the host, so the keep path was removing freshly pulled images the plain prune would hold.
This needs Docker 25+ when a keep setting is on; an older daemon logs that and skips the docker prune.

All listings now run before any removal, one container inspect replaces two, and docker_logged replaces
the repeated log and sed pairs. The test pins the difference between the stores with a save and load fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@azlekov
azlekov merged commit 0426df0 into main Nov 5, 2026
11 checks passed
@azlekov
azlekov deleted the feat/pruner-keep-images-containers branch November 5, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: let the pruner keep images and containers from other workloads

1 participant