diff --git a/cli/command/formatter/container.go b/cli/command/formatter/container.go index e17313a6dc12..346ac80a08b7 100644 --- a/cli/command/formatter/container.go +++ b/cli/command/formatter/container.go @@ -21,13 +21,14 @@ import ( const ( defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" - namesHeader = "NAMES" - commandHeader = "COMMAND" - runningForHeader = "CREATED" - mountsHeader = "MOUNTS" - localVolumes = "LOCAL VOLUMES" - networksHeader = "NETWORKS" - platformHeader = "PLATFORM" + namesHeader = "NAMES" + commandHeader = "COMMAND" + runningForHeader = "CREATED" + mountsHeader = "MOUNTS" + localVolumes = "LOCAL VOLUMES" + networksHeader = "NETWORKS" + platformHeader = "PLATFORM" + healthStatusHeader = "HEALTH STATUS" ) // Platform wraps a [ocispec.Platform] to implement the stringer interface. @@ -121,6 +122,7 @@ func NewContainerContext() *ContainerContext { "LocalVolumes": localVolumes, "Networks": networksHeader, "Platform": platformHeader, + "HealthStatus": healthStatusHeader, } return &containerCtx } @@ -352,6 +354,34 @@ func (c *ContainerContext) Networks() string { return strings.Join(networks, ",") } +// HealthStatus returns the container's health status (for example, "healthy","unhealthy", or "starting"). +// If no healthcheck is configured, an empty +// string is returned. +func (c *ContainerContext) HealthStatus() string { + if c.c.Health != nil && c.c.Health.Status != "" { + return string(c.c.Health.Status) + } + + // Fallback for API versions before v1.52, which include health only in Status text; + // see https://github.com/moby/moby/pull/50281 + // see https://github.com/moby/moby/blob/docker-v29.4.3/daemon/container/health.go#L18-L43 + _, health, ok := strings.Cut(c.c.Status, "(") + if !ok || !strings.HasSuffix(health, ")") { + return "" + } + + health = strings.TrimSuffix(health, ")") + health = strings.TrimPrefix(health, "health: ") + + parsedHealth := container.HealthStatus(health) + switch parsedHealth { + case container.Healthy, container.Unhealthy, container.Starting: + return health + default: + return "" + } +} + // DisplayablePorts returns formatted string representing open ports of container // e.g. "0.0.0.0:80->9090/tcp, 9988/tcp" // it's used by command 'docker ps' diff --git a/cli/command/formatter/container_test.go b/cli/command/formatter/container_test.go index fe7b57dd7f52..6a8c288beeb5 100644 --- a/cli/command/formatter/container_test.go +++ b/cli/command/formatter/container_test.go @@ -494,6 +494,7 @@ func TestContainerContextWriteJSON(t *testing.T) { { "Command": `""`, "CreatedAt": expectedCreated, + "HealthStatus": "", "ID": "containerID1", "Image": "ubuntu", "Labels": "", @@ -511,6 +512,7 @@ func TestContainerContextWriteJSON(t *testing.T) { { "Command": `""`, "CreatedAt": expectedCreated, + "HealthStatus": "", "ID": "containerID2", "Image": "ubuntu", "Labels": "", @@ -528,6 +530,7 @@ func TestContainerContextWriteJSON(t *testing.T) { { "Command": `""`, "CreatedAt": expectedCreated, + "HealthStatus": "", "ID": "containerID3", "Image": "ubuntu", "Labels": "", @@ -615,6 +618,7 @@ func TestContainerBackCompat(t *testing.T) { {field: "Image", expected: "docker.io/library/ubuntu"}, {field: "Command", expected: `"/bin/sh"`}, {field: "CreatedAt", expected: time.Unix(createdAtTime.Unix(), 0).String()}, + {field: "HealthStatus", expected: ""}, {field: "RunningFor", expected: "12 months ago"}, {field: "Ports", expected: "8080/tcp"}, {field: "Status", expected: "running"}, diff --git a/docs/reference/commandline/container_ls.md b/docs/reference/commandline/container_ls.md index a19f7a5e0410..a01a32869c08 100644 --- a/docs/reference/commandline/container_ls.md +++ b/docs/reference/commandline/container_ls.md @@ -405,6 +405,7 @@ Valid placeholders for the Go template are listed below: | `.Ports` | Exposed ports. | | `.State` | Container status (for example; "created", "running", "exited"). | | `.Status` | Container status with details about duration and health-status. | +| `.HealthStatus` | Container health status ("starting", "healthy", "unhealthy"; empty when unavailable). | | `.Size` | Container disk size. | | `.Names` | Container names. | | `.Labels` | All labels assigned to the container. |