cli icon indicating copy to clipboard operation
cli copied to clipboard

suggested improvements to `docker image ls` and `docker image ls --tree` for multi-platform

Open thaJeztah opened this issue 1 year ago • 0 comments

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;

docker image ls
REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
<none>           <none>    8262a6d8c38a   7 minutes ago    13.6MB
docker-cli-dev   latest    f5f0547476ee   12 minutes ago   762MB
nginx            alpine    2140dad235c1   2 weeks ago      76.7MB
alpine           latest    beefdbd8a1da   6 weeks ago      24.2MB

Sorting by the created date can be useful, e.g.;

  • The last built image appears at the top (so could be found through cutting the first line)
  • It's consistent with 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 where docker build was able to use the build-cache, in which case the image wasn't updated, and as a result the CREATED 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 a CREATED 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).

$ docker image ls --tree

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
└─ linux/arm64          18ca7881145d        762MB          179MB    ✔

nginx:alpine            2140dad235c1       76.7MB         21.5MB
├─ linux/arm64/v8       d1f949a77b81       76.7MB         21.5MB
├─ linux/amd64          ae136e431e76           0B             0B
└─ linux/s390x          8c310bf29cfa           0B             0B

alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ linux/riscv64        80cde017a105       10.6MB         3.37MB
├─ linux/arm64/v8       9cee2b382fe2       13.6MB         4.09MB
└─ linux/s390x          2b5b26e09ca2           0B             0B

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:

  • Whether an image was built locally or pulled (relevant w.r.t. garbage collection)
  • Dates related to the image: created date, last pulled, last pushed, last used, etc. (also see https://github.com/moby/moby/issues/4237).
  • More?

1. Add a collapsed version of the --tree view

We 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 the features option in the CLI config (e.g. {"features": {"multiplatform-output": true}}.

The collapsed view will have the same columns as the expanded --tree view;

$ docker image ls

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
<untagged>              20ad73eca911       13.6MB         4.09MB    ✔
<untagged>              b3e87f642f5c       13.6MB         4.09MB
<untagged>              8262a6d8c38a       13.6MB         4.09MB
docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
nginx:alpine            2140dad235c1       76.7MB         21.5MB
alpine:latest           beefdbd8a1da       24.2MB         7.46MB

This means that when using the --tree view, the layout stays the same, but with more details shown;

$ docker image ls --tree

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
<untagged>              20ad73eca911       13.6MB         4.09MB    ✔
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB    ✔

<untagged>              b3e87f642f5c       13.6MB         4.09MB
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB

<untagged>              8262a6d8c38a       13.6MB         4.09MB
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB

docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
└─ linux/arm64          18ca7881145d        762MB          179MB    ✔

nginx:alpine            2140dad235c1       76.7MB         21.5MB
├─ linux/arm64/v8       d1f949a77b81       76.7MB         21.5MB
├─ linux/amd64          ae136e431e76           0B             0B
├─ linux/arm/v6         ae1ee4b63c14           0B             0B
├─ linux/arm/v7         20ad73eca911           0B             0B
├─ linux/386            1e69bfb21757           0B             0B
├─ linux/ppc64le        7fef8bcf8b6c           0B             0B
└─ linux/s390x          8c310bf29cfa           0B             0B

alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ linux/riscv64        80cde017a105       10.6MB         3.37MB
├─ linux/arm64/v8       9cee2b382fe2       13.6MB         4.09MB
├─ linux/amd64          33735bd63cf8           0B             0B
├─ linux/arm/v6         50f635c8b04d           0B             0B
├─ linux/arm/v7         f2f82d424957           0B             0B
├─ linux/386            b3e87f642f5c           0B             0B
├─ linux/ppc64le        c7a6800e3dc5           0B             0B
└─ linux/s390x          2b5b26e09ca2           0B             0B

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:

$ docker image ls

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
nginx:alpine            2140dad235c1       76.7MB         21.5MB
<untagged>              8262a6d8c38a       13.6MB         4.09MB    ✔
<untagged>              b3e87f642f5c       13.6MB         4.09MB 
<untagged>              8262a6d8c38a       13.6MB         4.09MB 

This means that when using the --tree view, the layout stays the same, but with more details shown;

$ docker image ls --tree

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ linux/riscv64        80cde017a105       10.6MB         3.37MB
├─ linux/arm64/v8       9cee2b382fe2       13.6MB         4.09MB
├─ linux/amd64          33735bd63cf8           0B             0B
├─ linux/arm/v6         50f635c8b04d           0B             0B
├─ linux/arm/v7         f2f82d424957           0B             0B
├─ linux/386            b3e87f642f5c           0B             0B
├─ linux/ppc64le        c7a6800e3dc5           0B             0B
└─ linux/s390x          2b5b26e09ca2           0B             0B

docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
└─ linux/arm64          18ca7881145d        762MB          179MB    ✔

nginx:alpine            2140dad235c1       76.7MB         21.5MB
├─ linux/arm64/v8       d1f949a77b81       76.7MB         21.5MB
├─ linux/amd64          ae136e431e76           0B             0B
├─ linux/arm/v6         ae1ee4b63c14           0B             0B
├─ linux/arm/v7         20ad73eca911           0B             0B
├─ linux/386            1e69bfb21757           0B             0B
├─ linux/ppc64le        7fef8bcf8b6c           0B             0B
└─ linux/s390x          8c310bf29cfa           0B             0B

<untagged>              20ad73eca911       13.6MB         4.09MB    ✔
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB    ✔

<untagged>              b3e87f642f5c       13.6MB         4.09MB
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB

<untagged>              8262a6d8c38a       13.6MB         4.09MB
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB

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;

$ docker pull --platform=linux/riscv64 alpine:latest
$ docker pull --platform=linux/arm64 alpine:latest
$ docker image ls -a --tree alpine

IMAGE                   ID             DISK USAGE   CONTENT SIZE   IN USE
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ linux/riscv64        80cde017a105       10.6MB         3.37MB
├─ linux/arm64/v8       9cee2b382fe2       13.6MB         4.09MB
├─ linux/amd64          33735bd63cf8           0B             0B
├─ linux/arm/v6         50f635c8b04d           0B             0B
├─ linux/arm/v7         f2f82d424957           0B             0B
├─ linux/386            b3e87f642f5c           0B             0B
├─ linux/ppc64le        c7a6800e3dc5           0B             0B
└─ linux/s390x          2b5b26e09ca2           0B             0B

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:

  • https://github.com/moby/moby/pull/48701
$ docker pull --platform=linux/riscv64 alpine:latest
$ docker pull --platform=linux/arm64 alpine:latest
$ docker image ls -a --tree alpine

IMAGE                   ID             DISK USAGE   CONTENT SIZE   IN USE
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ 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       10.6MB         3.37MB
└─ linux/s390x          2b5b26e09ca2           0B             0B

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")

$ docker image ls --tree alpine

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ linux/arm64/v8       9cee2b382fe2       13.6MB         4.09MB
└─ linux/riscv64        80cde017a105       10.6MB         3.37MB

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).

$ docker image ls

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
nginx:alpine            2140dad235c1       76.7MB         21.5MB

Same, but --tree view:

$ docker image ls --tree

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ linux/arm64/v8       9cee2b382fe2       13.6MB         4.09MB
└─ linux/riscv64        80cde017a105       10.6MB         3.37MB

docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
└─ linux/arm64          18ca7881145d        762MB          179MB    ✔

nginx:alpine            2140dad235c1       76.7MB         21.5MB
└─ linux/arm64/v8       d1f949a77b81       76.7MB         21.5MB

And -a / --all option to show all images, including un-tagged and those not pulled;

$ docker image ls --tree

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
alpine:latest           beefdbd8a1da       24.2MB         7.46MB
├─ 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       10.6MB         3.37MB
└─ linux/s390x          2b5b26e09ca2           0B             0B

docker-cli-dev:latest   f5f0547476ee        762MB          179MB    ✔
└─ linux/arm64          18ca7881145d        762MB          179MB    ✔

nginx:alpine            2140dad235c1       76.7MB         21.5MB
├─ linux/amd64          ae136e431e76           0B             0B
├─ linux/arm/v6         ae1ee4b63c14           0B             0B
├─ linux/arm/v7         20ad73eca911           0B             0B
├─ linux/arm64/v8       d1f949a77b81       76.7MB         21.5MB
├─ linux/386            1e69bfb21757           0B             0B
├─ linux/ppc64le        7fef8bcf8b6c           0B             0B
└─ linux/s390x          8c310bf29cfa           0B             0B

<untagged>              20ad73eca911       13.6MB         4.09MB    ✔
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB    ✔

<untagged>              b3e87f642f5c       13.6MB         4.09MB
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB

<untagged>              8262a6d8c38a       13.6MB         4.09MB
└─ linux/arm64          1ab6fc68586e       13.6MB         4.09MB

Some parts may have to be looked into in more depth; there's still some odd behavior that, while "by design", is surprising;

  • https://github.com/moby/moby/issues/36435

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.

thaJeztah avatar Oct 20 '24 21:10 thaJeztah