Skip to content

Instantly share code, notes, and snippets.

@MichaelSimons
Last active December 17, 2024 00:38
Show Gist options
  • Save MichaelSimons/fb588539dcefd9b5fdf45ba04c302db6 to your computer and use it in GitHub Desktop.
Save MichaelSimons/fb588539dcefd9b5fdf45ba04c302db6 to your computer and use it in GitHub Desktop.
Retrieving Docker Image Sizes

Retrieving Docker Image Sizes

There are two metrics that are important to consider when discussing the size of Docker images.

  1. Compressed size - This is often referred to as the wire size. This affects how fast/slow images can be pulled from a registry. This impacts the first run experience on machines where images are not cached.
  2. Uncompressed size - This is often referred to as the size on disk. This affects how much local storage is required to support your Docker workloads.

The example commands shown below will work on Windows, MacOS, and Linux.

How to Measure the Compressed Size

Measuring the compressed size of a Docker image depends on where the image resides - in a Docker registry or locally.

Docker Registry Image

If the image resides in a registry, you can retrieve the size of the image without pulling it locally. To do this you can utilize the docker manifest command. At the time this article was written, the docker manifest command is in experimental and must be enabled. See experimental docker features for more information.

> docker manifest inspect mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim
  {
          "schemaVersion": 2,
          "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
          "config": {
                  "mediaType": "application/vnd.docker.container.image.v1+json",
                  "size": 3957,
                  "digest": "sha256:f1017ebdaf1137beb2e39ecb7e0541418accec7b4ea28800ffd948e6c8140cf2"
          },
          "layers": [
                  {
                          "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                          "size": 27092654,
                          "digest": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c"
                  },
                  {
                          "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                          "size": 17057932,
                          "digest": "sha256:db438065d0640cd860aed4addc2db1b55714063bc11ec86f47e9b55ba26c42e1"
                  },
                  {
                          "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                          "size": 1016592,
                          "digest": "sha256:e345d85b1d3e0c9edc36dad0f7a9cc3b7155dd98024d5773fdff02eac6550b94"
                  },
                  {
                          "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                          "size": 31200951,
                          "digest": "sha256:f6285e2730365f6529506d2445a0b4f9fd8a3e76fd5f3c6c268a2946cc08ba13"
                  },
                  {
                          "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                          "size": 98,
                          "digest": "sha256:c39dcdf2780c320c6cdbf3b014a980b067c0b872ae2b9ba5dc196050116bd2c0"
                  },
                  {
                          "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                          "size": 39727,
                          "digest": "sha256:da98c9d305deab5fbfb759a98b73e88ed6461ca8e2b43468d6abc047575dae60"
                  }
          ]
  }

By adding up the config and layer sizes you will get the total compressed size of the image. In this example, the compressed size of the mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim image is 76,411,911 bytes (74.6 MB).

Local Image

If you are building Docker images and want to get an ideal of what the compressed size, you could push it to a registry and then utilize the technique describe in the previous Docker Registry Image section to retrieve the compressed size, but that can be wasteful and/or undesirable. An alternative option is to use the docker save command to get a good approximation of the compressed size.

> docker save mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim -o samples-dotnetapp-buster-slim.tar
> docker run --rm -v d:\samples:/work debian:buster-slim gzip /work/samples-dotnetapp-buster-slim.tar
> ls .\samples-dotnetapp-buster-slim.tar.gz
  Directory: D:\temp\dantest

  Mode                LastWriteTime         Length Name
  ----                -------------         ------ ----
  -a----        12/6/2019   2:09 PM       73991595 samples-dotnetapp-buster-slim.tar.gz

You see here that the compressed size is reported as 73,991,595 bytes (72.3 MB). Keep in mind I said this technique would get a close approximate. This technique creates a single file versus a file per layer. As a result the single file is slightly smaller.

How to Measure the Uncompressed Size

To measure the uncompressed size of a Docker image, it is easiest to pull the image locally. You can then retrieve the size of the image with the docker inspect command.

> docker pull mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim
  dotnetapp-buster-slim: Pulling from dotnet/core/samples
  000eee12ec04: Pull complete
  db438065d064: Pull complete
  e345d85b1d3e: Pull complete
  f6285e273036: Pull complete
  c39dcdf2780c: Pull complete
  da98c9d305de: Pull complete
  Digest: sha256:1b1566f2b864c6fa0522d48f805a59b0a9e7da9eea75551d0603d8ef8159f04d
  Status: Downloaded newer image for mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim
  mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim
> docker inspect -f "{{ .Size }}" mcr.microsoft.com/dotnet/core/samples:dotnetapp-stretch
  189371468

You can see that the uncompressed size of the mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim image is 189,371,468 bytes (185 MB).

If you would like to get a more detailed breakdown of the image size by layer you can utilize the docker history command.

> docker history mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim
  IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
  f1017ebdaf11        13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["dotnet" "dot…   0B
  <missing>           13 days ago         /bin/sh -c #(nop) COPY dir:dddbbb7806aa5feae…   102kB
  <missing>           13 days ago         /bin/sh -c #(nop) WORKDIR /app                  0B
  <missing>           13 days ago         /bin/sh -c curl -SL --output dotnet.tar.gz h…   76.4MB
  <missing>           13 days ago         /bin/sh -c #(nop)  ENV DOTNET_VERSION=3.0.1     0B
  <missing>           13 days ago         /bin/sh -c apt-get update     && apt-get ins…   2.28MB
  <missing>           13 days ago         /bin/sh -c #(nop)  ENV ASPNETCORE_URLS=http:…   0B
  <missing>           13 days ago         /bin/sh -c apt-get update     && apt-get ins…   41.3MB
  <missing>           2 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B
  <missing>           2 weeks ago         /bin/sh -c #(nop) ADD file:bc8179c87c8dbb3d9…   69.2MB

If you don't like the truncation seen above, try using the --no-trunc option.

@sparty02
Copy link

Nice summary! For more human readable sizes on the uncompressed sizes, you can pipe it through numfmt:

❯ docker inspect -f "{{ .Size }}" mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim | numfmt --to=si
190M

@markuskreitzer
Copy link

docker image ls mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim works too. Uncompressed size.

@ddelange
Copy link

ddelange commented Jul 25, 2022

Getting compressed image size before pull for any registry that serves OCI image manifest or Docker image manifest version 2:

  • Uses docker manifest inspect (available by default in recent Docker versions)
  • Parses and sums layer sizes from the manifest using jq
  • Formats sizes to iec standard using numfmt (not si, sizes in manifests are 1024-based)
  • Supports multi-arch manifests
$ dockersize() { docker manifest inspect -v "$1" | jq -c 'if type == "array" then .[] else . end' |  jq -r '[ ( .Descriptor.platform | [ .os, .architecture, .variant, ."os.version" ] | del(..|nulls) | join("/") ), ( [ ( .OCIManifest // .SchemaV2Manifest ).layers[].size ] | add ) ] | join(" ")' | numfmt --to iec --format '%.2f' --field 2 | sort | column -t ; }

$ dockersize mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim
linux/amd64  72.96M

$ dockersize ghcr.io/ddelange/pycuda/runtime:3.9-master
linux/amd64  1.84G
linux/arm64  1.80G

$ dockersize python
linux/amd64                    334.98M
linux/arm/v5                   308.21M
linux/arm/v7                   295.69M
linux/arm64/v8                 326.32M
linux/386                      339.74M
linux/mips64le                 314.88M
linux/ppc64le                  343.86M
linux/s390x                    309.52M
windows/amd64/10.0.20348.825   2.20G
windows/amd64/10.0.17763.3165  2.54G
  • For Debian users: apt-get install jq
  • For Mac users: brew install coreutils jq (coreutils ships numfmt)

@ilyaevseev
Copy link

Read registry and print listing to STDOUT:

AUTH="$(jq -r '.auths["docker.my-registry.org"].auth' < ~/.docker/config.json | base64 -d)"
docker run --rm anoxis/registry-cli -r https://docker.my-registry.org -l "$AUTH"

Read listing from STDIN and print sizes to STDOUT:

#!/bin/sh

URL="${1:-docker.my-registry.org}"

while read k v unused; do
    test "$k" = "Image:" && IMG="$v" && continue
    test "$k" = "tag:"               || continue
    SZ="$(docker manifest inspect -v "$URL/$IMG:$v" 2>&1 | jq -r '[ .SchemaV2Manifest.layers[].size ] | add')"
    echo "$SZ" "$IMG:$v" ;;
done

Run it all:

print-registry-listing | print-docker-sizes | sort -nr | numfmt --to iec --format '%.2f' | column -t

@ericbl
Copy link

ericbl commented Feb 23, 2023

IMHO, this article is missing a point I've been myself missing for too long with Docker: the advantage of the layers in the docker images.
The "Uncompressed size - This is often referred to as the size on disk
This is not fully accurate, not to say wrong.
The uncompressed size is the "global size" of a docker image but not necessary how much it takes on the disk.
This size can be extracted from the manifest as the article suggests, or simply looking at the size column in
docker images

A complementary command is
docker system df -v ( see docker docs )
This is showing the "shared size" and the "unique size". The "global size" is the addition of both. But the "shared size" is used only once, not per image. This is very useful to better understand the real size on the disk!

Let's take an exemple (I just did this on my side): you create 2 Dockerfile with the same base (e.g. FROM debian:bullseye-YYYYMMDD-slim)
and then add some simple package into them. ( e.g. RUN apt-get update && apt-get install -y zip unzip && rm -rf /var/lib/apt/lists/* for one and same with whatever other package for the other)
Build these 2 images, and see their size with docker cmds:
docker images will show about 82 MB each.
docker system df -v will show a shared size (the shared layer of the FROM) about 80 MB and each one an unique size of ~ 2 MB.

At the end, both are taking ~84 MB and not 164 MB !

This concept can be a decisive point when you design a suite of multiple containers: try to have as much in common as possible, i.e. using a common base image. This also reduce a lot the advantage of Alpine (which base image is only 3-4 MB vs 80 for my debian example above)

@wokalek
Copy link

wokalek commented Jul 27, 2023

image

image

@drifter75
Copy link

docker manifest inspect -v ghcr.io/gethomepage/homepage:v0.7.3 | grep size | awk -F ':' '{sum+=$NF} END {print sum}' | numfmt --to=iec-i

@DracoBlue
Copy link

If you need compressed size, you can use this:

$ docker image save mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim | gzip | wc -c | numfmt --to=si
      74M

@brianclinkenbeard
Copy link

$ docker manifest inspect <repo> | jq -r '.config.size + ([.layers[].size] | add)' | numfmt --to=iec
100M

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment