Repository: docker/compose

Issue:
[BUG] docker compose images shows incorrect CREATED date ("292 years ago") because LastTagTime is unset
### Description

When running `docker compose images`, the` CREATED` column displays absurd values such as “292 years ago” instead of the actual image build time.

This happens because Compose is using the `LastTagTime` field from image metadata, which defaults to` 0001-01-01T00:00:00Z` if the image was pulled or built without a local tag event. The underlying image config has a valid` .Created` field, but this are ignored.

Expected behaviour:

docker compose images should display the actual build/creation time of the image, based on the image’s Created field

Actual behaviour:

docker compose images shows 292 years ago because it renders Metadata.LastTagTime, which defaults to 0001-01-01T00:00:00Z unless explicitly set.

Cause

In [cmd/compose/images.go](https://github.com/docker/compose/blob/main/cmd/compose/images.go), compose sets the Created column to i.LastTagTime:

imageList = append(imageList, img{
    ContainerName: ctr,
    ID:            i.ID,
    Repository:    i.Repository,
    Tag:           i.Tag,
    Platform:      platforms.Format(i.Platform),
    Size:          i.Size,
    LastTagTime:   i.LastTagTime,
})

This was introduced in commit [ea1c26d](https://github.com/docker/compose/commit/ea1c26d22a7781738cabf6007071d5957367786b).

### Steps To Reproduce

```
[testuser@vbox ]$ mkdir dockertest && cd dockertest
[testuser@vbox dockertest]$ vi docker-compose.yaml
```
Minimal yaml  for alpine:3.18
```
services:
  alpine:
    image: alpine:3.18
    command: sleep 3600
```

```
[testuser@vbox dockertest]$ 
[testuser@vbox dockertest]$ docker compose up -d
[+] Running 2/2
 ✔ alpine Pulled                                                                                                                                                                         3.0s 
   ✔ 44cf07d57ee4 Pull complete                                                                                                                                                          0.9s 
[+] Running 2/2
 ✔ Network dockertest_default     Created                                                                                                                                                0.4s 
 ✔ Container dockertest-alpine-1  Started                                                                                                                                                0.6s 
[testuser@vbox dockertest]$ docker compose images
CONTAINER             REPOSITORY          TAG                 PLATFORM            IMAGE ID            SIZE                CREATED
dockertest-alpine-1   alpine              3.18                linux/amd64         802c91d52981        7.36MB              292 years ago

[testuser@vbox dockertest]$ docker inspect alpine:3.18 | grep -i created
        "Created": "2025-02-14T03:03:06Z",

[testuser@vbox dockertest]$ docker inspect --format '{{json .Metadata}}' alpine:3.18
{"LastTagTime":"0001-01-01T00:00:00Z"}
```

### Compose Version

```Text
Docker Compose version v2.39.1

Client: Docker Engine - Community
 Version:           28.3.3
 API version:       1.51
 Go version:        go1.24.5
 Git commit:        980b856
 Built:             Fri Jul 25 11:37:02 2025
 OS/Arch:           linux/amd64
 Context:           default
```

### Docker Environment

```Text
Client: Docker Engine - Community
 Version:    28.3.3
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.26.1
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.39.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 19
  Running: 17
  Paused: 0
  Stopped: 2
 Images: 19
 Server Version: 28.3.3
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 CDI spec directories:
  /etc/cdi
  /var/run/cdi
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 05044ec0a9a75232cad458027ca83437aae3f4da
 runc version: v1.2.5-0-g59923ef
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 5.14.0-570.32.1.el9_6.x86_64
 Operating System: Rocky Linux 9.6 (Blue Onyx)
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 15.36GiB
 Name: vbox
 ID: cc1c9b11-effe-4d5e-a634-2657c29d70e1
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Username: ptcustomers
 Experimental: false
 Insecure Registries:
  ::1/128
  127.0.0.0/8
 Live Restore Enabled: false
```

### Anything else?

_No response_

Hints from issue comments:

Solution patch:
diff --git a/cmd/compose/images.go b/cmd/compose/images.go
index 5544204c..4f305e49 100644
--- a/cmd/compose/images.go
+++ b/cmd/compose/images.go
@@ -101,6 +101,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
 		// Convert map to slice
 		var imageList []img
 		for ctr, i := range images {
+			lastTagTime := i.LastTagTime
+			if lastTagTime.IsZero() {
+				lastTagTime = i.Created
+			}
 			imageList = append(imageList, img{
 				ContainerName: ctr,
 				ID:            i.ID,
@@ -108,7 +112,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
 				Tag:           i.Tag,
 				Platform:      platforms.Format(i.Platform),
 				Size:          i.Size,
-				LastTagTime:   i.LastTagTime,
+				LastTagTime:   lastTagTime,
 			})
 		}
 		json, err := formatter.ToJSON(imageList, "", "")
diff --git a/pkg/api/api.go b/pkg/api/api.go
index a48b5ab5..51140757 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -558,6 +558,7 @@ type ImageSummary struct {
 	Tag         string
 	Platform    platforms.Platform
 	Size        int64
+	Created     time.Time
 	LastTagTime time.Time
 }
 
diff --git a/pkg/compose/images.go b/pkg/compose/images.go
index a91d6159..d49e92c6 100644
--- a/pkg/compose/images.go
+++ b/pkg/compose/images.go
@@ -22,6 +22,7 @@ import (
 	"slices"
 	"strings"
 	"sync"
+	"time"
 
 	"github.com/containerd/errdefs"
 	"github.com/containerd/platforms"
@@ -90,6 +91,11 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
 				}
 			}
 
+			created, err := time.Parse(time.RFC3339Nano, image.Created)
+			if err != nil {
+				return err
+			}
+
 			mux.Lock()
 			defer mux.Unlock()
 			summary[getCanonicalContainerName(c)] = api.ImageSummary{
@@ -103,6 +109,7 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
 					Variant:      image.Variant,
 				},
 				Size:        image.Size,
+				Created:     created,
 				LastTagTime: image.Metadata.LastTagTime,
 			}
 			return nil



Test patch:
diff --git a/pkg/compose/images_test.go b/pkg/compose/images_test.go
index a7a1d5e9..85a2b577 100644
--- a/pkg/compose/images_test.go
+++ b/pkg/compose/images_test.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
@@ -44,8 +45,12 @@ func TestImages(t *testing.T) {
 	args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
 	listOpts := container.ListOptions{All: true, Filters: args}
 	api.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{APIVersion: "1.96"}, nil).AnyTimes()
-	image1 := imageInspect("image1", "foo:1", 12345)
-	image2 := imageInspect("image2", "bar:2", 67890)
+	timeStr1 := "2025-06-06T06:06:06.000000000Z"
+	created1, _ := time.Parse(time.RFC3339Nano, timeStr1)
+	timeStr2 := "2025-03-03T03:03:03.000000000Z"
+	created2, _ := time.Parse(time.RFC3339Nano, timeStr2)
+	image1 := imageInspect("image1", "foo:1", 12345, timeStr1)
+	image2 := imageInspect("image2", "bar:2", 67890, timeStr2)
 	api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil).MaxTimes(2)
 	api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(image2, nil)
 	c1 := containerDetail("service1", "123", "running", "foo:1")
@@ -62,32 +67,36 @@ func TestImages(t *testing.T) {
 			Repository: "foo",
 			Tag:        "1",
 			Size:       12345,
+			Created:    created1,
 		},
 		"456": {
 			ID:         "image2",
 			Repository: "bar",
 			Tag:        "2",
 			Size:       67890,
+			Created:    created2,
 		},
 		"789": {
 			ID:         "image1",
 			Repository: "foo",
 			Tag:        "1",
 			Size:       12345,
+			Created:    created1,
 		},
 	}
 	assert.NilError(t, err)
 	assert.DeepEqual(t, images, expected)
 }
 
-func imageInspect(id string, imageReference string, size int64) image.InspectResponse {
+func imageInspect(id string, imageReference string, size int64, created string) image.InspectResponse {
 	return image.InspectResponse{
 		ID: id,
 		RepoTags: []string{
 			"someRepo:someTag",
 			imageReference,
 		},
-		Size: size,
+		Size:    size,
+		Created: created,
 	}
 }