From a20eb45b26aa600cb2e942eac743b2d54445d01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Tue, 6 Aug 2024 14:46:04 +0200 Subject: [PATCH 1/3] image/save: Add `--platform` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski Signed-off-by: Sebastiaan van Stijn Signed-off-by: Sebastiaan van Stijn --- cli/command/image/save.go | 20 +++++++-- cli/command/image/save_test.go | 16 +++++++ docs/reference/commandline/image_save.md | 56 ++++++++++++++++++++++-- docs/reference/commandline/save.md | 7 +-- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/cli/command/image/save.go b/cli/command/image/save.go index c5a32859b8..dfceecc865 100644 --- a/cli/command/image/save.go +++ b/cli/command/image/save.go @@ -4,6 +4,7 @@ import ( "context" "io" + "github.com/containerd/platforms" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -13,8 +14,9 @@ import ( ) type saveOptions struct { - images []string - output string + images []string + output string + platform string } // NewSaveCommand creates a new `docker save` command @@ -38,7 +40,10 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT") + flags.StringVar(&opts.platform, "platform", "", `Save only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`) + _ = flags.SetAnnotation("platform", "version", []string{"1.48"}) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) return cmd } @@ -52,7 +57,16 @@ func RunSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error return errors.Wrap(err, "failed to save image") } - responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, image.SaveOptions{}) + var options image.SaveOptions + if opts.platform != "" { + p, err := platforms.Parse(opts.platform) + if err != nil { + return errors.Wrap(err, "invalid platform") + } + options.Platform = &p + } + + responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options) if err != nil { return err } diff --git a/cli/command/image/save_test.go b/cli/command/image/save_test.go index 8a399bdfa6..65d931566a 100644 --- a/cli/command/image/save_test.go +++ b/cli/command/image/save_test.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/image" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -51,6 +52,11 @@ func TestNewSaveCommandErrors(t *testing.T) { args: []string{"-o", "/dev/null", "arg1"}, expectedError: "failed to save image: invalid output path: \"/dev/null\" must be a directory or a regular file", }, + { + name: "invalid platform", + args: []string{"--platform", "", "arg1"}, + expectedError: `invalid platform`, + }, } for _, tc := range testCases { tc := tc @@ -95,6 +101,16 @@ func TestNewSaveCommandSuccess(t *testing.T) { return io.NopCloser(strings.NewReader("")), nil }, }, + { + args: []string{"--platform", "linux/amd64", "arg1"}, + isTerminal: false, + imageSaveFunc: func(images []string, options image.SaveOptions) (io.ReadCloser, error) { + assert.Assert(t, is.Len(images, 1)) + assert.Check(t, is.Equal("arg1", images[0])) + assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform)) + return io.NopCloser(strings.NewReader("")), nil + }, + }, } for _, tc := range testCases { tc := tc diff --git a/docs/reference/commandline/image_save.md b/docs/reference/commandline/image_save.md index d6df5574c1..0a01c45b93 100644 --- a/docs/reference/commandline/image_save.md +++ b/docs/reference/commandline/image_save.md @@ -9,9 +9,10 @@ Save one or more images to a tar archive (streamed to STDOUT by default) ### Options -| Name | Type | Default | Description | -|:-----------------|:---------|:--------|:-----------------------------------| -| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | +| Name | Type | Default | Description | +|:--------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| +| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | +| [`--platform`](#platform) | `string` | | Save only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | @@ -59,3 +60,52 @@ You can even cherry-pick particular tags of an image repository. ```console $ docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy ``` + +### Save a specific platform (--platform) + +The `--platform` option allows you to specify which platform variant of the +image to save. By default, `docker save` saves all platform variants that +are present in the daemon's image store. Use the `--platform` option +to specify which platform variant of the image to save. An error is produced +if the given platform is not present in the local image store. + +The platform option takes the `os[/arch[/variant]]` format; for example, +`linux/amd64` or `linux/arm64/v8`. Architecture and variant are optional, +and default to the daemon's native architecture if omitted. + +The following example pulls the RISC-V variant of the `alpine:latest` image +and saves it to a tar archive. + +```console +$ docker pull --platform=linux/riscv64 alpine:latest +latest: Pulling from library/alpine +8c4a05189a5f: Download complete +Digest: sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d +Status: Downloaded newer image for alpine:latest +docker.io/library/alpine:latest + +$ docker image save --platform=linux/riscv64 -o alpine-riscv.tar alpine:latest + +$ ls -lh image.tar +-rw------- 1 thajeztah staff 3.9M Oct 7 11:06 alpine-riscv.tar +``` + +The following example attempts to save a platform variant of `alpine:latest` +that doesn't exist in the local image store, resulting in an error. + +```console +$ docker image ls --tree +IMAGE ID DISK USAGE CONTENT SIZE IN USE +alpine:latest beefdbd8a1da 10.6MB 3.37MB +├─ linux/riscv64 80cde017a105 10.6MB 3.37MB +├─ linux/amd64 33735bd63cf8 0B 0B +├─ linux/arm/v6 50f635c8b04d 0B 0B +├─ linux/arm/v7 f2f82d424957 0B 0B +├─ linux/arm64/v8 9cee2b382fe2 0B 0B +├─ linux/386 b3e87f642f5c 0B 0B +├─ linux/ppc64le c7a6800e3dc5 0B 0B +└─ linux/s390x 2b5b26e09ca2 0B 0B + +$ docker image save --platform=linux/s390x -o alpine-s390x.tar alpine:latest +Error response from daemon: no suitable export target found for platform linux/s390x +``` diff --git a/docs/reference/commandline/save.md b/docs/reference/commandline/save.md index 1c9f9c53db..19e8c353da 100644 --- a/docs/reference/commandline/save.md +++ b/docs/reference/commandline/save.md @@ -9,9 +9,10 @@ Save one or more images to a tar archive (streamed to STDOUT by default) ### Options -| Name | Type | Default | Description | -|:-----------------|:---------|:--------|:-----------------------------------| -| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | +| Name | Type | Default | Description | +|:-----------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| +| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | +| `--platform` | `string` | | Save only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | From b0bb4ba7f2ab00594f64e34c2be9bd07de1dc722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Tue, 6 Aug 2024 14:48:39 +0200 Subject: [PATCH 2/3] image/load: Add `--platform` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski Signed-off-by: Sebastiaan van Stijn --- cli/command/image/load.go | 23 ++++++++--- cli/command/image/load_test.go | 18 +++++++++ .../load-command-success.with platform.golden | 1 + docs/reference/commandline/image_load.md | 38 +++++++++++++++++-- docs/reference/commandline/load.md | 9 +++-- 5 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 cli/command/image/testdata/load-command-success.with platform.golden diff --git a/cli/command/image/load.go b/cli/command/image/load.go index 190e9302a3..0bd09c67ee 100644 --- a/cli/command/image/load.go +++ b/cli/command/image/load.go @@ -4,6 +4,7 @@ import ( "context" "io" + "github.com/containerd/platforms" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -15,8 +16,9 @@ import ( ) type loadOptions struct { - input string - quiet bool + input string + quiet bool + platform string } // NewLoadCommand creates a new `docker load` command @@ -40,7 +42,10 @@ func NewLoadCommand(dockerCli command.Cli) *cobra.Command { flags.StringVarP(&opts.input, "input", "i", "", "Read from tar archive file, instead of STDIN") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the load output") + flags.StringVar(&opts.platform, "platform", "", `Load only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`) + _ = flags.SetAnnotation("platform", "version", []string{"1.48"}) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) return cmd } @@ -63,12 +68,20 @@ func runLoad(ctx context.Context, dockerCli command.Cli, opts loadOptions) error return errors.Errorf("requested load from stdin, but stdin is empty") } - var loadOpts image.LoadOptions + var options image.LoadOptions if opts.quiet || !dockerCli.Out().IsTerminal() { - loadOpts.Quiet = true + options.Quiet = true } - response, err := dockerCli.Client().ImageLoad(ctx, input, loadOpts) + if opts.platform != "" { + p, err := platforms.Parse(opts.platform) + if err != nil { + return errors.Wrap(err, "invalid platform") + } + options.Platform = &p + } + + response, err := dockerCli.Client().ImageLoad(ctx, input, options) if err != nil { return err } diff --git a/cli/command/image/load_test.go b/cli/command/image/load_test.go index 30edd87789..5b0e4532e8 100644 --- a/cli/command/image/load_test.go +++ b/cli/command/image/load_test.go @@ -8,8 +8,10 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/image" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/golden" ) @@ -40,6 +42,14 @@ func TestNewLoadCommandErrors(t *testing.T) { return image.LoadResponse{}, errors.Errorf("something went wrong") }, }, + { + name: "invalid platform", + args: []string{"--platform", ""}, + expectedError: `invalid platform`, + imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) { + return image.LoadResponse{}, nil + }, + }, } for _, tc := range testCases { tc := tc @@ -96,6 +106,14 @@ func TestNewLoadCommandSuccess(t *testing.T) { return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil }, }, + { + name: "with platform", + args: []string{"--platform", "linux/amd64"}, + imageLoadFunc: func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) { + assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform)) + return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil + }, + }, } for _, tc := range testCases { tc := tc diff --git a/cli/command/image/testdata/load-command-success.with platform.golden b/cli/command/image/testdata/load-command-success.with platform.golden new file mode 100644 index 0000000000..51da4200ab --- /dev/null +++ b/cli/command/image/testdata/load-command-success.with platform.golden @@ -0,0 +1 @@ +Success \ No newline at end of file diff --git a/docs/reference/commandline/image_load.md b/docs/reference/commandline/image_load.md index 8b675a3bb1..bd104ed352 100644 --- a/docs/reference/commandline/image_load.md +++ b/docs/reference/commandline/image_load.md @@ -9,10 +9,11 @@ Load an image from a tar archive or STDIN ### Options -| Name | Type | Default | Description | -|:------------------------------------|:---------|:--------|:---------------------------------------------| -| [`-i`](#input), [`--input`](#input) | `string` | | Read from tar archive file, instead of STDIN | -| `-q`, `--quiet` | `bool` | | Suppress the load output | +| Name | Type | Default | Description | +|:------------------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| +| [`-i`](#input), [`--input`](#input) | `string` | | Read from tar archive file, instead of STDIN | +| [`--platform`](#platform) | `string` | | Load only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | +| `-q`, `--quiet` | `bool` | | Suppress the load output | @@ -58,3 +59,32 @@ fedora 20 58394af37342 7 weeks ago fedora heisenbug 58394af37342 7 weeks ago 385.5 MB fedora latest 58394af37342 7 weeks ago 385.5 MB ``` + + +### Load a specific platform (--platform) + +The `--platform` option allows you to specify which platform variant of the +image to load. By default, `docker load` loads all platform variants that +are present in the archive. Use the `--platform` option to specify which +platform variant of the image to load. An error is produced if the given +platform is not present in the archive. + +The platform option takes the `os[/arch[/variant]]` format; for example, +`linux/amd64` or `linux/arm64/v8`. Architecture and variant are optional, +and default to the daemon's native architecture if omitted. + +The following example loads the `linux/amd64` variant of an `alpine` image +from an archive that contains multiple platform variants. + +```console +$ docker image load -i image.tar --platform=linux/amd64 +Loaded image: alpine:latest +``` + +The following example attempts to load a `linux/ppc64le` image from an +archive, but the given platform is not present in the archive; + +```console +$ docker image load -i image.tar --platform=linux/ppc64le +requested platform (linux/ppc64le) not found: image might be filtered out +``` diff --git a/docs/reference/commandline/load.md b/docs/reference/commandline/load.md index c961f06020..41251b1b13 100644 --- a/docs/reference/commandline/load.md +++ b/docs/reference/commandline/load.md @@ -9,10 +9,11 @@ Load an image from a tar archive or STDIN ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:---------------------------------------------| -| `-i`, `--input` | `string` | | Read from tar archive file, instead of STDIN | -| `-q`, `--quiet` | `bool` | | Suppress the load output | +| Name | Type | Default | Description | +|:----------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| +| `-i`, `--input` | `string` | | Read from tar archive file, instead of STDIN | +| `--platform` | `string` | | Load only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | +| `-q`, `--quiet` | `bool` | | Suppress the load output | From d085e2445c676d09b6999f5f4fa6801aa61efda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Thu, 8 Aug 2024 12:44:24 +0200 Subject: [PATCH 3/3] image/history: Add `--platform` flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski Signed-off-by: Sebastiaan van Stijn --- cli/command/image/history.go | 19 +++++- cli/command/image/history_test.go | 18 ++++++ .../history-command-success.platform.golden | 2 + docs/reference/commandline/history.md | 1 + docs/reference/commandline/image_history.md | 64 +++++++++++++++++-- 5 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 cli/command/image/testdata/history-command-success.platform.golden diff --git a/cli/command/image/history.go b/cli/command/image/history.go index 73a893fa97..91a09e6be7 100644 --- a/cli/command/image/history.go +++ b/cli/command/image/history.go @@ -3,17 +3,20 @@ package image import ( "context" + "github.com/containerd/platforms" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types/image" + "github.com/pkg/errors" "github.com/spf13/cobra" ) type historyOptions struct { - image string + image string + platform string human bool quiet bool @@ -45,12 +48,24 @@ func NewHistoryCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show image IDs") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output") flags.StringVar(&opts.format, "format", "", flagsHelper.FormatHelp) + flags.StringVar(&opts.platform, "platform", "", `Show history for the given platform. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`) + _ = flags.SetAnnotation("platform", "version", []string{"1.48"}) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) return cmd } func runHistory(ctx context.Context, dockerCli command.Cli, opts historyOptions) error { - history, err := dockerCli.Client().ImageHistory(ctx, opts.image, image.HistoryOptions{}) + var options image.HistoryOptions + if opts.platform != "" { + p, err := platforms.Parse(opts.platform) + if err != nil { + return errors.Wrap(err, "invalid platform") + } + options.Platform = &p + } + + history, err := dockerCli.Client().ImageHistory(ctx, opts.image, options) if err != nil { return err } diff --git a/cli/command/image/history_test.go b/cli/command/image/history_test.go index b4c519de29..0a69df8d15 100644 --- a/cli/command/image/history_test.go +++ b/cli/command/image/history_test.go @@ -8,8 +8,10 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/image" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/golden" ) @@ -33,6 +35,11 @@ func TestNewHistoryCommandErrors(t *testing.T) { return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong") }, }, + { + name: "invalid platform", + args: []string{"--platform", "", "arg1"}, + expectedError: `invalid platform`, + }, } for _, tc := range testCases { tc := tc @@ -89,6 +96,17 @@ func TestNewHistoryCommandSuccess(t *testing.T) { }}, nil }, }, + { + name: "platform", + args: []string{"--platform", "linux/amd64", "image:tag"}, + imageHistoryFunc: func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) { + assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform)) + return []image.HistoryResponseItem{{ + ID: "1234567890123456789", + Created: time.Now().Unix(), + }}, nil + }, + }, } for _, tc := range testCases { tc := tc diff --git a/cli/command/image/testdata/history-command-success.platform.golden b/cli/command/image/testdata/history-command-success.platform.golden new file mode 100644 index 0000000000..2d963d3248 --- /dev/null +++ b/cli/command/image/testdata/history-command-success.platform.golden @@ -0,0 +1,2 @@ +IMAGE CREATED CREATED BY SIZE COMMENT +123456789012 Less than a second ago 0B diff --git a/docs/reference/commandline/history.md b/docs/reference/commandline/history.md index 7705c70bca..daa2fb9a0a 100644 --- a/docs/reference/commandline/history.md +++ b/docs/reference/commandline/history.md @@ -14,6 +14,7 @@ Show the history of an image | `--format` | `string` | | Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates | | `-H`, `--human` | `bool` | `true` | Print sizes and dates in human readable format | | `--no-trunc` | `bool` | | Don't truncate output | +| `--platform` | `string` | | Show history for the given platform. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | | `-q`, `--quiet` | `bool` | | Only show image IDs | diff --git a/docs/reference/commandline/image_history.md b/docs/reference/commandline/image_history.md index 7ed258a9f5..1412dbdd45 100644 --- a/docs/reference/commandline/image_history.md +++ b/docs/reference/commandline/image_history.md @@ -9,12 +9,13 @@ Show the history of an image ### Options -| Name | Type | Default | Description | -|:----------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`--format`](#format) | `string` | | Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates | -| `-H`, `--human` | `bool` | `true` | Print sizes and dates in human readable format | -| `--no-trunc` | `bool` | | Don't truncate output | -| `-q`, `--quiet` | `bool` | | Only show image IDs | +| Name | Type | Default | Description | +|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`--format`](#format) | `string` | | Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates | +| `-H`, `--human` | `bool` | `true` | Print sizes and dates in human readable format | +| `--no-trunc` | `bool` | | Don't truncate output | +| [`--platform`](#platform) | `string` | | Show history for the given platform. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | +| `-q`, `--quiet` | `bool` | | Only show image IDs | @@ -76,3 +77,54 @@ $ docker history --format "{{.ID}}: {{.CreatedSince}}" busybox f6e427c148a7: 4 weeks ago : 4 weeks ago ``` + +### Show history for a specific platform (--platform) + +The `--platform` option allows you to specify which platform variant to show +history for if multiple platforms are present. By default, `docker history` +shows the history for the daemon's native platform or if not present, the +first available platform. + +If the local image store has multiple platform variants of an image, the +`--platform` option selects which variant to show the history for. An error +is produced if the given platform is not present in the local image cache. + +The platform option takes the `os[/arch[/variant]]` format; for example, +`linux/amd64` or `linux/arm64/v8`. Architecture and variant are optional, +and if omitted falls back to the daemon's defaults. + + +The following example pulls the RISC-V variant of the `alpine:latest` image +and shows its history. + + +```console +$ docker image pull --quiet --platform=linux/riscv64 alpine +docker.io/library/alpine:latest + +$ docker image history --platform=linux/s390x alpine +IMAGE CREATED CREATED BY SIZE COMMENT +beefdbd8a1da 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B + 3 weeks ago /bin/sh -c #(nop) ADD file:ba2637314e600db5a… 8.46MB +``` + +The following example attempts to show the history for a platform variant of +`alpine:latest` that doesn't exist in the local image store, resulting in +an error. + +```console +$ docker image ls --tree +IMAGE ID DISK USAGE CONTENT SIZE IN USE +alpine:latest beefdbd8a1da 10.6MB 3.37MB +├─ linux/riscv64 80cde017a105 10.6MB 3.37MB +├─ linux/amd64 33735bd63cf8 0B 0B +├─ linux/arm/v6 50f635c8b04d 0B 0B +├─ linux/arm/v7 f2f82d424957 0B 0B +├─ linux/arm64/v8 9cee2b382fe2 0B 0B +├─ linux/386 b3e87f642f5c 0B 0B +├─ linux/ppc64le c7a6800e3dc5 0B 0B +└─ linux/s390x 2b5b26e09ca2 0B 0B + +$ docker image history --platform=linux/s390x alpine +Error response from daemon: image with reference alpine:latest was found but does not match the specified platform: wanted linux/s390x +```