Skip to content

Fix reclaimable image disk usage calculation for in-use images#51778

Merged
thaJeztah merged 1 commit intomoby:masterfrom
austinvazquez:fix-reclaimable-image-disk-usage
Apr 2, 2026
Merged

Fix reclaimable image disk usage calculation for in-use images#51778
thaJeztah merged 1 commit intomoby:masterfrom
austinvazquez:fix-reclaimable-image-disk-usage

Conversation

@austinvazquez
Copy link
Copy Markdown
Contributor

@austinvazquez austinvazquez commented Dec 19, 2025

- What I did

This change reworks to build up the reclaimable image disk usage instead of setting it to total size and subtracting active images.

- How I did it

- How to verify it

- Human readable description for the release notes

Fix  `/system/df` reporting in-use images as reclaimable

- A picture of a cute animal (not mandatory but encouraged)

@austinvazquez austinvazquez self-assigned this Dec 19, 2025
@austinvazquez austinvazquez force-pushed the fix-reclaimable-image-disk-usage branch from 63e1ad4 to 9dc6860 Compare December 19, 2025 17:05
@austinvazquez
Copy link
Copy Markdown
Contributor Author

Opening up early to get some eyes on it + run the full test suite.

I need a bit more time to write some integration tests for the daemon. Also I have this annoying issue where because of the swap from reclaimable = total - inUse to reclaimable = 0 + unused. The df output is showing 99% due to the minor delta. My hunch is it might be containerd backend metadata diff. Need to look into it some more though.

root@62916345377d:/go/src/github.com/docker/docker# docker image ls
                                                                                                                                                                       i Info →   U  In Use
IMAGE           ID             DISK USAGE   CONTENT SIZE   EXTRA
ubuntu:latest   c35e29c94501        141MB         30.8MB        
root@62916345377d:/go/src/github.com/docker/docker# docker container ls
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
root@62916345377d:/go/src/github.com/docker/docker# docker system df 
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          1         0         141.2MB   141.2MB (99%)
Containers      0         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     0         0         0B        0B
root@62916345377d:/go/src/github.com/docker/docker# 

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the reclaimable image disk usage calculation for in-use images by changing from a subtraction-based approach to an additive approach. Previously, the code initialized Reclaimable to the total size and subtracted active images, which could lead to incorrect calculations when images had invalid size values (-1).

  • Changed calculation logic to build up reclaimable space by adding unused images instead of subtracting used images
  • Applied the fix to both daemon/disk_usage.go and client/system_disk_usage.go
  • Added comprehensive test coverage for various image disk usage scenarios

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
daemon/disk_usage.go Updated to calculate reclaimable space by accumulating unused images instead of subtracting active images from total
client/system_disk_usage.go Applied the same calculation fix to the legacy API conversion function
client/system_disk_usage_test.go Added new test suite covering no images, images without containers, images with containers, and images with shared size scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@thaJeztah thaJeztah added this to the 29.2.0 milestone Dec 19, 2025
@austinvazquez austinvazquez force-pushed the fix-reclaimable-image-disk-usage branch 2 times, most recently from 0977c7f to c1ab10d Compare December 19, 2025 23:10
@austinvazquez
Copy link
Copy Markdown
Contributor Author

Through some debug logs, I found the delta was 6688 bytes for the ubuntu:latest image which was pointed out to me to be the exact size of the image index.
Ref: https://oci.dag.dev/?image=ubuntu%3Alatest

@austinvazquez austinvazquez force-pushed the fix-reclaimable-image-disk-usage branch 3 times, most recently from 7fff3bb to bcb1c1a Compare December 20, 2025 03:52
Comment thread daemon/disk_usage.go Outdated
Comment on lines +88 to +90
// Also include the size of image index if it was included
if i.Descriptor != nil && i.Descriptor.MediaType == ocispec.MediaTypeImageIndex {
du.Reclaimable += i.Descriptor.Size
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Through my testing, I found the reclaimable size is off if the image contains an OCI image index. Docker schema v2 images; however, do not need to include the top level descriptor size. Open to thoughts if there is a better option to compute this value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code, shouldn't the descriptor size also be added to TotalSize ? Before this change, we'd start with both Reclaimable and TotalSize to be set to totalSize (calculated through daemon.imageService.ImageDiskUsage(ctx)). After this change, we correct the Reclaimable size, but don't adjust TotalSize.

Wondering if the actual issue is that daemon.imageService.ImageDiskUsage(ctx) doesn't include the manifest size, or does that function not have access to the descriptors?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The containerd image service is computing the total size as the sum of all layers and the size of any content for the image including the image index. (quick-link

visitedImages := make(map[digest.Digest]struct{})
for _, img := range imgs {
if err := i.walkPresentChildren(ctx, img.Target, func(ctx context.Context, desc ocispec.Descriptor) error {
if _, ok := visitedImages[desc.Digest]; ok {
return nil
}
visitedImages[desc.Digest] = struct{}{}
diskUsage += desc.Size
return nil
}); err != nil {
return 0, err
}
}
)

Perhaps a better fix would be to have the image summary Size field include the size of additional content including the image index.

@austinvazquez austinvazquez marked this pull request as ready for review December 20, 2025 04:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread daemon/disk_usage.go
Comment thread client/system_disk_usage.go
@vvoland vvoland modified the milestones: 29.2.0, 29.3.0 Jan 22, 2026
@vvoland vvoland modified the milestones: 29.3.0, 29.2.1 Jan 29, 2026
@vvoland vvoland modified the milestones: 29.3.0, 29.4.0 Feb 27, 2026
@austinvazquez austinvazquez force-pushed the fix-reclaimable-image-disk-usage branch 2 times, most recently from a9e9dc1 to 73b0c3d Compare March 12, 2026 16:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread daemon/disk_usage.go Outdated
Comment thread client/system_disk_usage.go Outdated
Comment on lines +226 to +251
{
name: "multiplatform image with an image index",
mockResponse: &legacyDiskUsage{
LayersSize: 4608,
Images: []image.Summary{
{ID: "image1", Size: 4096, SharedSize: 0, Containers: 0, Descriptor: &ocispec.Descriptor{MediaType: ocispec.MediaTypeImageIndex, Size: 512}},
},
},
expectedActiveCount: 0,
expectedTotalCount: 1,
expectedReclaimable: 4608, // (4096 - 0) + 512
expectedTotalSize: 4608,
},
{
name: "multiplatform image with a Docker distribution manifest",
mockResponse: &legacyDiskUsage{
LayersSize: 4096,
Images: []image.Summary{
{ID: "image1", Size: 4096, SharedSize: 0, Containers: 0, Descriptor: &ocispec.Descriptor{MediaType: "application/vnd.docker.distribution.manifest.v2+json", Size: 427}},
},
},
expectedActiveCount: 0,
expectedTotalCount: 1,
expectedReclaimable: 4096, // (4096 - 0)
expectedTotalSize: 4096,
},
@austinvazquez austinvazquez force-pushed the fix-reclaimable-image-disk-usage branch from 5aa5866 to 3072f1e Compare March 19, 2026 21:27
This change reworks to build up the reclaimable image disk uage instead of setting it to total size and subtracting active images. This change also includes the image index size as reclaimable if it is included with the image summary.

Signed-off-by: Austin Vazquez <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread daemon/disk_usage.go
du.Reclaimable -= i.Size - i.SharedSize
if i.Containers > 0 {
du.ActiveCount++
} else if i.Size != -1 && i.SharedSize != -1 {
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image.Summary.Containers uses -1 as a sentinel for “not set” (see image list code paths). With the current if i.Containers > 0 { … } else if i.Size != -1 … { du.Reclaimable += … }, images with Containers == -1 will be treated as unused and counted as reclaimable, which can reintroduce “in-use images shown as reclaimable” when container-count is unavailable.

Consider tightening the reclaimable branch to only run when i.Containers == 0 (and optionally skipping reclaimable calculation when i.Containers < 0).

Suggested change
} else if i.Size != -1 && i.SharedSize != -1 {
} else if i.Containers == 0 && i.Size != -1 && i.SharedSize != -1 {

Copilot uses AI. Check for mistakes.
Comment on lines +250 to +251
} else if i.Size != -1 && i.SharedSize != -1 {
// Only count reclaimable size if we have size information
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image.Summary.Containers can be -1 to indicate “not set”. In that case, the current if i.Containers > 0 { … } else if i.Size != -1 … { idu.Reclaimable += … } will treat Containers == -1 as unused and count it as reclaimable.

To avoid incorrect reclaimable reporting when container-count is unknown, change the reclaimable branch to require i.Containers == 0 (and optionally skip reclaimable calculation for i.Containers < 0).

Suggested change
} else if i.Size != -1 && i.SharedSize != -1 {
// Only count reclaimable size if we have size information
} else if i.Containers == 0 && i.Size != -1 && i.SharedSize != -1 {
// Only count reclaimable size if we have size information and container-count is known to be zero

Copilot uses AI. Check for mistakes.
@thaJeztah
Copy link
Copy Markdown
Member

LOL; told ya! Once it starts reviewing it will continue providing comments; at least one new (nit-pick) for every review cycle 😆😆

Copy link
Copy Markdown
Contributor

@vvoland vvoland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@thaJeztah thaJeztah modified the milestones: 29.4.0, 29.3.2 Apr 1, 2026
Copy link
Copy Markdown
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, we can look at follow-ups for the CoPilot comments

@thaJeztah thaJeztah merged commit ad1d80c into moby:master Apr 2, 2026
216 of 219 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

daemon: /system/df reports in-use images as reclaimable

4 participants