✦ For everyone, free.

Practical knowledge for real and everyday life

Home

20.2.2.4 Image Size Practice

A focused guide to Image Size Practice, connecting core concepts with practical Docker and container operations.

Image size practice is the hands-on work of measuring, diagnosing, and reducing Docker image sizes using the techniques available in the Docker toolchain. Knowing that layer caching, multi-stage builds, and minimal base images reduce size is different from applying them systematically to a real image and verifying the results. This practice provides the specific commands and diagnostic steps to analyze what is in an image and where its size comes from.

Measuring Image Size

The starting point for any optimization effort is knowing the current size:

docker images
REPOSITORY   TAG           IMAGE ID       CREATED         SIZE
my-api       unoptimized   a1b2c3d4e5f6   2 minutes ago   1.14GB
my-api       optimized     b2c3d4e5f6a1   1 minute ago    98MB

To list all images sorted by size:

docker images --format "{{.Repository}}:{{.Tag}}\t{{.Size}}" | sort -k2 -rh

Inspecting Layer Sizes

docker history shows the size contribution of each layer in the image:

docker history my-api:unoptimized
IMAGE          CREATED BY                                           SIZE
a1b2c3d4e5f6   /bin/sh -c #(nop)  CMD ["node" "dist/server.js"]   0B
b2c3d4e5f6a1   /bin/sh -c #(nop)  EXPOSE 3000                     0B
c3d4e5f6a1b2   /bin/sh -c #(nop)  COPY dir:... in /app            5.2MB
d3e4f5a6b7c8   /bin/sh -c npm install                              320MB
e4f5a6b7c8d9   /bin/sh -c #(nop)  COPY file:... in /app           2.1KB
f5a6b7c8d9e0   /bin/sh -c #(nop)  WORKDIR /app                    0B
a6b7c8d9e0f1   /bin/sh -c #(nop)  FROM node:20                    1.1GB

Reading from the bottom: the base image contributes 1.1GB, npm install adds 320MB, and source files add 5.2MB. The total is dominated by the full Node.js base image and the complete dev dependency tree.

For the optimized image:

docker history my-api:optimized
IMAGE          CREATED BY                                           SIZE
...            /bin/sh -c #(nop)  CMD ...                          0B
...            /bin/sh -c #(nop)  COPY ...                         45.2MB
...            /bin/sh -c npm ci --only=production                 52.1MB
...            /bin/sh -c #(nop)  WORKDIR /app                     0B
...            /bin/sh -c #(nop)  FROM node:20-alpine              135MB

The base image is now 135MB instead of 1.1GB. The npm ci --only=production layer installs only production dependencies. The total is 232MB before compression.

Examining What Is Inside an Image

To explore the contents of an image without running a full container, run an ephemeral shell:

docker run --rm -it my-api:unoptimized sh

Inside the container, identify large directories:

du -sh /app/* | sort -rh | head -20
315M    /app/node_modules
5.2M    /app/dist
2.1K    /app/package.json

The node_modules directory at 315MB is the largest contributor. In the optimized image, only production dependencies are present:

docker run --rm -it my-api:optimized sh
du -sh /app/node_modules
48M    /app/node_modules

An 84% reduction in node_modules size by excluding dev dependencies.

Identifying Unnecessarily Included Files

Check whether build artifacts that should have been excluded by .dockerignore ended up in the image:

docker run --rm my-api:unoptimized ls /app

Look for directories that should not be present:

  • __tests__ or test/ — test files have no place in a production image
  • .git — version control history is never needed at runtime
  • node_modules from the host — if the host's node_modules was copied instead of installed inside the container
docker run --rm my-api:unoptimized ls /app/node_modules | wc -l

Counts the number of packages in node_modules. Compare against a build with npm ci --only=production to verify dev dependencies are absent.

Checking Whether Package Manager Caches Were Cleaned

For images using apt-get:

docker run --rm my-image:latest ls /var/lib/apt/lists/

If this directory is non-empty, the package manager cache was not cleaned in the same RUN layer that installed packages. The cache is consuming image space without providing any runtime value.

Correct approach:

RUN apt-get update \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

For pip:

docker run --rm my-python-image:latest ls ~/.cache/pip

Use --no-cache-dir to prevent pip from caching:

RUN pip install --no-cache-dir -r requirements.txt

Side-by-Side Comparison Workflow

Build two versions of the same application: the naive version and the optimized version:

docker build -f Dockerfile.naive -t my-api:naive .
docker build -f Dockerfile.optimized -t my-api:optimized .
docker images | grep my-api
my-api   naive      a1b2c3d4   1.14GB
my-api   optimized  b2c3d4e5   98MB

Document the size reduction after each optimization step:

  1. Baseline (full node:20 + all deps + source): 1.14GB
  2. After switching to node:20-alpine: 320MB (72% reduction)
  3. After adding .dockerignore: 290MB (skipping node_modules from host)
  4. After --only=production: 175MB
  5. After multi-stage build: 98MB (91% total reduction)

Using Docker Scout for Size and CVE Analysis

Docker Scout provides a detailed breakdown of what is in an image:

docker scout cves my-api:unoptimized
docker scout cves my-api:optimized

The CVE count typically drops significantly with the optimized image because fewer packages are present.

docker scout recommendations my-api:unoptimized

Scout suggests alternative base images or configuration changes that would reduce size or CVE count, including estimated size changes for each recommendation.

Practical Size Targets

Application typeRealistic optimized size
Node.js (Alpine + prod deps + compiled)80–200MB
Python (slim + pip install)100–250MB
Go (scratch + static binary)5–30MB
Java (JRE Alpine + JAR)150–300MB
Static file server (nginx:alpine + files)20–50MB

These ranges account for production dependency trees. Images that exceed these targets by 2x or more typically have an optimization opportunity in the base image choice, dependency exclusion, or build artifact cleanup.