Open thaJeztah opened 1 month ago
OK, was giving this some thought, following the discussion on https://github.com/docker/cli/pull/5556#pullrequestreview-2382098624.
Taking the following situation;
docker pull --quiet alpine:latest
docker tag alpine:latest docker.io/namespace/image:1
docker tag alpine:latest docker.io/namespace/image:1.0
docker tag alpine:latest docker.io/namespace/image:1.0.0
docker tag alpine:latest docker.io/namespace/image:latest
docker tag alpine:latest internal.example.com/namespace/image:1
docker tag alpine:latest internal.example.com/namespace/image:1.0
docker tag alpine:latest internal.example.com/namespace/image:1.0.0
docker tag alpine:latest internal.example.com/namespace/image:latest
Currently, this shows as:
$ docker image ls --tree
IMAGE ID DISK USAGE CONTENT SIZE USED
alpine:latest
namespace/image:1
namespace/image:1.0
namespace/image:1.0.0
namespace/image:latest
internal.example.com/namespace/image:1
internal.example.com/namespace/image:1.0
internal.example.com/namespace/image:1.0.0
internal.example.com/namespace/image:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
We can split the list per repository, so that alpine
, namespace/image
and internal.example.com/namespace/image
don't get clobbered together:
$ docker image ls --tree
IMAGE ID DISK USAGE CONTENT SIZE USED
alpine:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
namespace/image:1
namespace/image:1.0
namespace/image:1.0.0
namespace/image:latest
namespace/image:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
internal.example.com/namespace/image:1
internal.example.com/namespace/image:1.0
internal.example.com/namespace/image:1.0.0
internal.example.com/namespace/image:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
That would still make things slightly cluttered, so we could go one level deeper on trees, and if a repository has multiple tags for the same image, show those as a nesting one level deeper;
$ docker image ls --tree
IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED
alpine:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ namespace/image:1 beefdbd8a1da - -
├─ namespace/image:1.0 beefdbd8a1da - -
├─ namespace/image:1.0.0 beefdbd8a1da - -
└─ namespace/image:latest beefdbd8a1da - -
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
internal.example.com/namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ internal.example.com/namespace/image:1 beefdbd8a1da - -
├─ internal.example.com/namespace/image:1.0 beefdbd8a1da - -
├─ internal.example.com/namespace/image:1.0.0 beefdbd8a1da - -
└─ internal.example.com/namespace/image:latest beefdbd8a1da - -
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
IMO neither of the "Split per repository"
$ docker image ls --tree
IMAGE ID DISK USAGE CONTENT SIZE USED
alpine:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
namespace/image:1
namespace/image:1.0
namespace/image:1.0.0
namespace/image:latest
namespace/image:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
internal.example.com/namespace/image:1
internal.example.com/namespace/image:1.0
internal.example.com/namespace/image:1.0.0
internal.example.com/namespace/image:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
or "Tree per repository"
$ docker image ls --tree
IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED
alpine:latest beefdbd8a1da 13.6MB 4.09MB
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ namespace/image:1 beefdbd8a1da - -
├─ namespace/image:1.0 beefdbd8a1da - -
├─ namespace/image:1.0.0 beefdbd8a1da - -
└─ namespace/image:latest beefdbd8a1da - -
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
internal.example.com/namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ internal.example.com/namespace/image:1 beefdbd8a1da - -
├─ internal.example.com/namespace/image:1.0 beefdbd8a1da - -
├─ internal.example.com/namespace/image:1.0.0 beefdbd8a1da - -
└─ internal.example.com/namespace/image:latest beefdbd8a1da - -
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
are good options – there's no visual indication that these are all actually the same image, and an unsuspecting user (or me, if I wasn't paying close attention) would quickly glance here and presume that I have three different images stored rather than one image with a number of different tags.
Particularly for the "tree per repository" option, I think the output quickly becomes unintelligibly long. Consider
$ docker image ls --tree
IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED
namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ namespace/image:1 beefdbd8a1da - -
├─ namespace/image:1.0 beefdbd8a1da - -
├─ namespace/image:1.0.0 beefdbd8a1da - -
└─ namespace/image:latest beefdbd8a1da - -
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
internal.example.com/namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ internal.example.com/namespace/image:1 beefdbd8a1da - -
├─ internal.example.com/namespace/image:1.0 beefdbd8a1da - -
├─ internal.example.com/namespace/image:1.0.0 beefdbd8a1da - -
└─ internal.example.com/namespace/image:latest beefdbd8a1da - -
├─ linux/amd64 33735bd63cf8 0B 0B
├─ linux/arm/v6 50f635c8b04d 0B 0B
├─ linux/arm/v7 f2f82d424957 0B 0B
├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
├─ linux/386 b3e87f642f5c 0B 0B
├─ linux/ppc64le c7a6800e3dc5 0B 0B
├─ linux/riscv64 80cde017a105 0B 0B
└─ linux/s390x 2b5b26e09ca2 0B 0B
namespace/other-image 768e5c6f5cb6 13.6MB 4.09MB
├─ namespace/other-image:1 768e5c6f5cb6 - -
├─ namespace/other-image:1.0 768e5c6f5cb6 - -
├─ namespace/other-image:1.0.0 768e5c6f5cb6 - -
└─ namespace/other-image:latest 768e5c6f5cb6 - -
├─ linux/amd64 7615936bd840 0B 0B
├─ linux/arm/v6 22f27168517d 0B 0B
├─ linux/arm/v7 dc3bab2a4285 0B 0B
├─ linux/arm64/v8 a9fc789b4096 13.6MB 4.09MB
├─ linux/386 98c3f7257909 0B 0B
├─ linux/ppc64le a4683230268f 0B 0B
├─ linux/riscv64 0e1d386b0b5d 0B 0B
└─ linux/s390x b129ffc84b55 0B 0B
internal.example.com/namespace/other-image 768e5c6f5cb6 13.6MB 4.09MB
├─ internal.example.com/namespace/other-image:1 768e5c6f5cb6 - -
├─ internal.example.com/namespace/other-image:1.0 768e5c6f5cb6 - -
├─ internal.example.com/namespace/other-image:1.0.0 768e5c6f5cb6 - -
└─ internal.example.com/namespace/other-image:latest 768e5c6f5cb6 - -
├─ linux/amd64 7615936bd840 0B 0B
├─ linux/arm/v6 22f27168517d 0B 0B
├─ linux/arm/v7 dc3bab2a4285 0B 0B
├─ linux/arm64/v8 a9fc789b4096 13.6MB 4.09MB
├─ linux/386 98c3f7257909 0B 0B
├─ linux/ppc64le a4683230268f 0B 0B
├─ linux/riscv64 0e1d386b0b5d 0B 0B
└─ linux/s390x b129ffc84b55 0B 0B
I only have two indexes in that output, but it's more than double as tall as my console normally is.
Right, but consider if those would all be a single manifest, then the current view for the above would look like;
$ docker image ls --tree
IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED
namespace/image:1
namespace/image:1.0
namespace/image:1.0.0
namespace/image:latest
internal.example.com/namespace/image:1
internal.example.com/namespace/image:1.0
internal.example.com/namespace/image:1.0.0
internal.example.com/namespace/image:latest
namespace/other-image:1
namespace/other-image:1.0
namespace/other-image:1.0.0
namespace/other-image:latest
internal.example.com/namespace/other-image:1
internal.example.com/namespace/other-image:1.0
internal.example.com/namespace/other-image:1.0.0
internal.example.com/namespace/other-image:latest 768e5c6f5cb6 13.6MB 4.09MB
├─ linux/amd64 7615936bd840 0B 0B
├─ linux/arm/v6 22f27168517d 0B 0B
├─ linux/arm/v7 dc3bab2a4285 0B 0B
├─ linux/arm64/v8 a9fc789b4096 13.6MB 4.09MB
├─ linux/386 98c3f7257909 0B 0B
├─ linux/ppc64le a4683230268f 0B 0B
├─ linux/riscv64 0e1d386b0b5d 0B 0B
└─ linux/s390x b129ffc84b55 0B 0B
I don't think that's worse than the proposed alternatives 🙈
But I guess we could make the grouping configurable, so we can all be happy? 😅
$ docker image ls --tree
IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED
namespace/image:1
namespace/image:1.0
namespace/image:1.0.0
namespace/image:latest
internal.example.com/namespace/image:1
internal.example.com/namespace/image:1.0
internal.example.com/namespace/image:1.0.0
internal.example.com/namespace/image:latest
namespace/other-image:1
namespace/other-image:1.0
namespace/other-image:1.0.0
namespace/other-image:latest
internal.example.com/namespace/other-image:1
internal.example.com/namespace/other-image:1.0
internal.example.com/namespace/other-image:1.0.0
internal.example.com/namespace/other-image:latest 768e5c6f5cb6 13.6MB 4.09MB
├─ linux/amd64 7615936bd840 0B 0B
├─ linux/arm/v6 22f27168517d 0B 0B
├─ linux/arm/v7 dc3bab2a4285 0B 0B
├─ linux/arm64/v8 a9fc789b4096 13.6MB 4.09MB
├─ linux/386 98c3f7257909 0B 0B
├─ linux/ppc64le a4683230268f 0B 0B
├─ linux/riscv64 0e1d386b0b5d 0B 0B
└─ linux/s390x b129ffc84b55 0B 0B
IMO that's a bit better than the other case – sure, it's not as "pretty" but it makes it clear it's all the same image and ultimately uses less space/I can see most of that in a console the size I normally use.
but it makes it clear it's all the same image
So that's the bit I'm really not sure about. How much should we consider them the same image ? There's many scenarios where they are not the same, and where we actually want to track / manage them separate;
internal.example.com/namespace/other-image:1
, but didn't push namespace/other-image:1
namespace/other-image:1
, but not allowed to use internal.example.com/namespace/other-image:1
There's just too many scenarios where, even though they may be sharing the same content under the hood, they are separate things to manage.
I do agree about the verbosity (output can get lengthy), which is why we could consider having the tree output default to "only show what's present";
$ docker image ls --tree
IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED
alpine:latest beefdbd8a1da 13.6MB 4.09MB
└─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
namespace/image beefdbd8a1da 13.6MB 4.09MB
├─ namespace/image:1 beefdbd8a1da - -
├─ namespace/image:1.0 beefdbd8a1da - -
├─ namespace/image:1.0.0 beefdbd8a1da - -
└─ namespace/image:latest beefdbd8a1da - -
└─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB
Grouping by namespace/image
(with the same digest) could make management easier when combined with;
In that case, things like this could be an option;
# push all the tags for the same image
$ docker push --all-tags namespace/image:1
# remove all the tags
$ docker image rm --all-tags namespace/image:1
e.g., if I pushed internal.example.com/namespace/other-image:1, but didn't push namespace/other-image:1 my company may have RAM, in which case I may be allowed to use namespace/other-image:1, but not allowed to use internal.example.com/namespace/other-image:1
That's definitely a tough question. IMO the "instinctive" model I have for this is, whether you say Laura
or laurazard
or some nickname, it's all referring to the same person. On the other hand, what you say about RAM and other cases is true. I'd generally be tempted to say that these are still the same image, you just can't pull it from some repo or push it to some repo, but I definitely see your perspective.
Description
The tree output currently uses the same sort order as the existing non-tree output, and orders the images by "created" time in descending order;
Sorting by the created date can be useful, e.g.;
docker container ls
/docker ps
, which uses the same ordering, but also provides flags to get the last containers (-l
,--latest
, and-n
,--last
)However, the
CREATED
has become less useful in various situations. For example, "reproducible builds" tend to either leave the "created" date unset, or use a fixed date (often resulting in images created "many years ago").The date also has some ambiguity; for multi-platform images, each image can have its own "created" date (and built at a different time); which date to show in the list?
The
CREATED
date also has been confusing at times. For example, an image that was just pulled may have been built weeks ago, and now not showing at the top of the list. But also situations wheredocker build
was able to use the build-cache, in which case the image wasn't updated, and as a result theCREATED
date of the image being in the past.Finally, sorting by
CREATED
is confusing when using the new--tree
output of images. This output does not currently have aCREATED
column, which makes the output order seem "random". With the tree view being more verbose, it may also be harder to find back images in the list when they're not sorted in an easy to discover way (some platforms were omitted in the example below to keep the example short).Suggestions
I think there's a couple of options we have, short-term and longer term.
Short term
We should be careful changing the default order, at least for the current presentation, as people may depend on this. However, the work on the
--tree
output is part of future work to make the CLI more human-friendly, and to provide a more modern look and feel. Such changes are a good opportunity to make changes; those changes may be "breaking" changes, so we may need some opt-in/opt-out options to help people transition.Longer term
We may need more granular information about dates and usage of images (and other content), such as:
1. Add a collapsed version of the
--tree
viewWe should make a collapsed version of the
--tree
view. This layout can become the default in future, but initially (and while we're still designing these bits), we can make this an "opt-in" through thefeatures
option in the CLI config (e.g.{"features": {"multiplatform-output": true}}
.The collapsed view will have the same columns as the expanded
--tree
view;This means that when using the
--tree
view, the layout stays the same, but with more details shown;2. Sort alphabetically by default
For the new layout, we can change the sort-order. I suggest that sorting alphabetically (using natural-sort) would make sense. I think we should also consider sorting
<untagged>
images last, as they may be less relevant to the user:This means that when using the
--tree
view, the layout stays the same, but with more details shown;3. Use stable sort order for manifests
The
--tree
option on currently sorts manifests to put those that are present first, and those that are not present (not pulled) after. The intent was to present "available" images at the top of each tree, followed by images that were not pulled.However, there's some limitations to this. First of all, the current approach makes the output non-deterministic as the order in which variants are pulled determines the order in which they're presented, i.e., the last pulled variant is returned first;
This makes the output non-deterministic, and lacking a
LAST PULLED
(or something similar) field, can make it somewhat confusing.The order in which variants appear in the manifest can be relevant, as in some cases this order affects what image is pulled as "best match" if no exact match is available for the host's native architecture, and if multiple platforms variants are candidates.
I think we should default to present variants in the order they are included in the manifest index. More details also in this PR:
4. Hide non-pulled images by default (TBD)
One option worth considering is to hide non-pulled platform variants by default. Doing so would partially achieve the goal that sorting the "available variants first" mentioned above, and it would make the output shorter in most situations. In many cases, users may only have the native variant of an image pulled.
We need to design a UX for this though; would
--all
be used to show "all the things", or do we need a more granular option? ("all variants" vs "all images, including dangling ones")5. Hide untagged images by default
We should consider re-defining the meaning of
--all
, as well as "dangling" and "intermediate" images. Some of the "dangling" images definition originates from the classic/legacy builder, which used the image store for build-cache; each step in the Dockerfile resulted in an untagged image, but the image had to be preserved to be used for its cache. Cleaning up those images was a manual step, so the--all
option was added to make them visible.With BuildKit being the default builder now, cleaning up the build-cache is handled separate from cleaning up images (
docker builder prune
/docker buildx prune
), and BuildKit also provides automatic garbage collection of its buildcache.While we don't (yet!) have automatic garbage collection for images, I think we should have that at some point (at least opt-in), and hiding untagged content would (IMO) be a good place to start, to show that's content eligible for removal (if it's important to you, you should've put a ring on it, and tagged or pushed it).
Same, but
--tree
view:And
-a
/--all
option to show all images, including un-tagged and those not pulled;Some parts may have to be looked into in more depth; there's still some odd behavior that, while "by design", is surprising;
Related to that, we need to decide whether to hide untagged images if they are still in use by a container, or consider those case (as they (I think) require
docker image rm --force
) are still eligible for garbage collecting (once the container is removed0, and thus should also be hidden.