Docker History Analyzer

Find where an image's size came from. Paste the output of docker history, ideally with --no-trunc so the commands are not cut off.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Analyze. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

An install followed by a cleanup

Two layers, both kept, and the second saved nothing at all

IMAGE      CREATED       CREATED BY                                    SIZE
abc123     2 days ago    /bin/sh -c apt-get install -y build-essential  280MB
<missing>  2 days ago    /bin/sh -c rm -rf /var/lib/apt/lists/*        0B
<missing>  2 days ago    /bin/sh -c #(nop) COPY dir:abc in /app        12MB

A build argument in the history

Readable by anybody who can pull, and reported here by name only

<missing>  1 day ago   |1 NPM_TOKEN=REPLACE_ME /bin/sh -c npm ci   40MB

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Adding a cleanup RUN to shrink an image

    The files are already in an earlier layer. The cleanup adds a layer recording deletions and changes the download size not at all.

    Instead:Do the install and the cleanup in one RUN joined with &&, or use a multi-stage build.

  2. Reading <missing> as a problem

    It is what every pulled image looks like below the top layer, because parent ids are not transferred by a pull.

    Instead:Ignore it. Build locally if you need the intermediate ids for something.

  3. Comparing history sizes to the registry's numbers

    History is extracted size and the registry reports compressed blobs. They measure different things.

    Instead:Use history for disk and the manifest for pull time.

Every layer is kept, so a later delete saves nothing

A file added in one layer and removed in a later one is still in the image and still downloaded on every pull. The removal only hides it from the filesystem view.

Why the three-instruction pattern is expensive

RUN install, RUN build, RUN clean-up produces three layers, and the cleanup layer records deletions without removing the bytes. The same commands joined with && in one RUN produce one layer that never contained the intermediate files. That difference is routinely hundreds of megabytes.

Multi-stage is the bigger win

Build in one stage and COPY only the artefact into a clean final stage. Nothing from the build stage is in the final image at all, so there is nothing to clean up and no history to leak.

<missing> is normal

Intermediate image ids are only kept for images built locally. For anything pulled from a registry the parent ids are not transferred, so every layer below the top shows <missing>. It does not mean anything is absent or corrupt.

These sizes are the extracted layers

docker history reports what the layer occupies on disk. The manifest reports the compressed blob, which is what crosses the network. The two never agree and neither is wrong.

A build argument's value is recorded here

docker history is exactly this data, so anybody who can pull the image can read it. The value is recorded whether or not the file it wrote survived, which is why a later cleanup does not help.

Use --no-trunc

The default output shortens the command column to fit the terminal, which usually removes the part that explains the size, and hides anything further along the line.

More docker tools

Docker Container Image Reference Parser Is that a registry or a Docker Hub user? Dockerfile Linter What this image will do to you later docker run to Kubernetes YAML And the flags that have no equivalent Docker Compose to Kubernetes And why depends_on has no equivalent Docker Logging Driver Generator json-file never rotates Dockerfile Non-Root USER Generator Numeric, or Kubernetes rejects it Docker OCI Image Labels Generator One of them actually does something Docker Hub Pull Rate Limit Calculator Counted per IP, not per person Docker Registry Storage Calculator Deleting a tag frees nothing Docker Build Context Analyzer All of it uploads before anything runs Docker Compose to docker run Converter Names stop resolving Docker Compose Version Migrator version: is obsolete now Docker Compose Network and Port Viewer Everything reaches everything Docker Command Explainer Which flags hand over the host Docker BuildKit Cache Mount Generator apt deletes the cache you just mounted Docker BuildKit Secret Mount Generator ARG is in docker history forever Docker .dockerignore Pattern Tester *.log does not match logs/app.log Docker Image Manifest Viewer unknown/unknown is not broken Docker Image Config Viewer Anyone who can pull can read your Env Docker Registry Token Decoder Check the scope before the credentials Docker HEALTHCHECK Generator Docker does nothing when it fails Docker Network Subnet Calculator 31 networks, then it stops Docker Run to Compose Converter Some flags have no equivalent Docker CMD and ENTRYPOINT Tester Why docker stop takes ten seconds Docker Port Mapping Tester -p reaches past your firewall Docker -v to --mount Converter -v invents missing directories Docker Restart Policy Simulator The backoff resets after ten seconds Docker Exit Code Explainer 137 is two different problems Docker CPU Limit Converter --cpu-shares limits nothing Docker Memory Limit Calculator -m 512m allows a gigabyte Docker Compose Interpolation Tester An unset variable is an empty string Docker Environment Variable Precedence Tester Your shell does not reach the container Docker Compose Override Merge Previewer An override can add a port, never remove one Dockerfile Multi-Stage Build Analyzer Which stages reach the image Dockerignore Validator It is not .gitignore

Elsewhere on the site