Skip to content

fix(image): pin the image a container runs to its digest - #5125

Open
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:fix/image-in-use-digest
Open

fix(image): pin the image a container runs to its digest#5125
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:fix/image-in-use-digest

Conversation

@ekalinin

@ekalinin ekalinin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

nerdctl images marks an image as in use by resolving the image name recorded on the container. A name is a mutable reference: container.Image(ctx) looks it up in the image store as it is now, not as it was when the container was created. Once a tag is moved, the U indicator lands on the wrong row.

 $ nerdctl tag alpine:3.13 mytag:latest
 $ nerdctl run -d --name c1 mytag:latest sleep infinity
 $ nerdctl tag nginx:alpine mytag:latest
 $ nerdctl images
 IMAGE           ID              DISK USAGE    CONTENT SIZE    EXTRA
 alpine:3.13     09538a1f51d3    5.9MB         2.7MB
 nginx:alpine    0168606be231    22.4MB        9.1MB           U

Both answers are wrong. The container still runs alpine, containerd still holds its snapshot and its layers, so alpine cannot be removed - yet it is shown as free, while nginx is shown as busy although nothing ever ran it. A container whose tag was removed altogether fails to resolve and drops out of the in-use set entirely.

The fix

Record the image target digest on the container at creation time, in a new nerdctl/image-digest label, and use it for the in-use lookup. The digest comes from the image nerdctl has already resolved, so nothing extra is fetched, and the label is read from the metadata the listing has already loaded (WithoutRefreshedMetadata), so the lookup costs no additional round trip.

Compatibility

Containers created before this label existed, or created outside nerdctl (ctr, kubelet, another client), have no such label. For those the previous behavior is kept as a fallback: they are still resolved by name. No state migration is needed and no existing container changes behavior for the worse.

An unparsable label value falls back the same way rather than dropping the container from the in-use set. Dropping it would be the more harmful failure: the image would look free, and anything built on this lookup would offer to reclaim space that is actually held.

Why it matters beyond the indicator

The same lookup backs the ACTIVE and RECLAIMABLE columns of nerdctl system df (follow-up, #3942). There a misattributed container is not a single letter in a column: the unique layers of an image that is actually in use get counted as reclaimable space, which is exactly the number a user acts on when deciding what to delete.

Tests

  • TestPinnedImageDigest covers the four label states: pinned, absent, empty, unparsable.
  • TestImages/In use survives a retag reproduces the scenario end to end and asserts both sides: the image the container runs keeps U, the image the tag now points at does not.

The in-use lookup was introduced in #5093.

`nerdctl images` marks an image as in use by resolving the image name stored on the
container, which follows the tag wherever it points now. After `nerdctl tag` moves a tag
onto another image, the container gets attributed to an image it never ran: the U
indicator lands on the wrong row.

Record the image target digest on the container at creation time, in a new
nerdctl/image-digest label, and use it for the in-use lookup. Containers created before
this label existed, or created outside nerdctl, are still resolved by name; an unparsable
value falls back the same way rather than dropping the container from the set.

This also matters for the ACTIVE and RECLAIMABLE columns of `nerdctl system df`, which
build on the same lookup.

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
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.

1 participant