Status: accepted
Every merge ships a runtime image, so a developer who keeps up accumulates a
local store of images that lost the latest tag and nothing else — see
Superseded Image. Reclaiming them is
uncontroversial. What is not is how to establish that one is safe to remove,
because a toolbox container outlives its session: containers are named per
(workspace, profile) and reconnected later, so an image with no tag and no
attached shell may still be the image a stopped container of a different
workspace expects to find when its developer comes back tomorrow.
The obvious defence is a census: list the containers, collect the images they
reference, subtract, remove the rest. We rejected it. The census cannot
actually establish what it appears to establish, because between our
ContainerList and our ImageRemove another toolbox shell on the same
machine can create a container from an image the census had just cleared —
the check is time-of-check-to-time-of-use and remains so however carefully it
is written. It buys a list, a comparison and their tests, and closes none of
the window it exists to close.
So Image Reclamation performs no
in-use check of its own. It calls ImageRemove with neither force nor
PruneChildren and treats the resulting error as the answer to the question
it did not ask. The daemon already refuses to remove an image any container
references — running or exited — and it refuses atomically, on the far side
of the race a census sits on the wrong side of. Deleting our own check does
not weaken the guarantee; it replaces an advisory one with the authoritative
one.
The absent flags are the load-bearing part of this decision, and both are
easy to add back by someone who reads the call as merely timid. force
converts every refusal above into a silent removal of an image a colleague's
stopped session is waiting for, which is the one outcome this whole act must
never produce. PruneChildren reaches past the candidate into the images
built on top of it — which on this project is exactly the :local overlay a
developer's own ~/.toolbox/Dockerfile produced, unreproducible from the
registry. Neither flag has a use here that is worth its failure mode.
Two, both uncomfortable, and neither visible at the call site:
A stopped container pins its image indefinitely. A developer with many workspaces will reclaim little or nothing until those containers are removed, because each one is a legitimate reference and the daemon says so. This is the correct behaviour and it will be reported as a bug.
An overlay pins its base the same way. :local is built FROM the base
image's ID, so the base has a child and the daemon refuses to remove it.
Anyone using an overlay Dockerfile reclaims nothing until they rebuild.
PruneChildren would "fix" this by destroying the overlay, which is why it
is not set.
Both are consequences of the contract rather than gaps in it. The remedy, if
one is ever wanted, is an explicit toolbox prune the developer types with
the consequences in front of them — not a flag flipped inside a sweep that
runs unattended at every shell start.
The argument above rests on a claim about Docker's behaviour, not about our
code: that the daemon refuses to remove an image referenced by a container
that is merely stopped. A fake client cannot testify to it — it would only
confirm that the fake does what we told it. The claim is therefore pinned by
a real-daemon gate (-tags dockergate, run in CI alongside the other gates)
that creates a container, runs it to exit, and asserts that an unforced
ImageRemove fails while that stopped container still references the image —
then succeeds once it is gone, which is what attributes the refusal to the
container rather than to anything else about the image. Unlike its two
neighbours it needs no bind mounts, so it is also runnable on a developer's
machine with the socket and IMAGE_TAG in hand; the container's own
created state is deliberately not what is asserted, since created and
exited are distinct to the daemon and the consequence below is the latter. Without that test this document asserts something nobody has checked.