Skip to main content

How to inspect Docker image metadata?

Inspecting Docker image metadata is what you do before pulling, after pulling, when debugging "why did this image grow?", or when you want to know exactly what an image promises to do at runtime. Three CLI tools cover the territory.

Theory

TL;DR

  • docker inspect = full JSON dump of an image's config, layers, labels, architecture, size.
  • docker history = chronological list of layers with the instruction that produced each.
  • docker manifest inspect = the registry-level manifest (multi-arch info, layer digests).
  • dive (third-party) = interactive TUI showing layer-by-layer file changes.
  • All work locally; some work against remote images via the registry.

docker inspect

The primary command. Returns a 100+ line JSON document. Use --format to extract specific fields:

bash
# Everything (a lot) docker inspect nginx:1.27 # Specific fields docker inspect nginx:1.27 --format '{{.Id}}' docker inspect nginx:1.27 --format '{{.Architecture}}' docker inspect nginx:1.27 --format '{{.Size}}' docker inspect nginx:1.27 --format '{{.Config.Cmd}}' docker inspect nginx:1.27 --format '{{.Config.Entrypoint}}' docker inspect nginx:1.27 --format '{{.Config.User}}' docker inspect nginx:1.27 --format '{{.Config.WorkingDir}}' # JSON of a section docker inspect nginx:1.27 --format '{{json .Config.Env}}' | jq docker inspect nginx:1.27 --format '{{json .Config.ExposedPorts}}' | jq docker inspect nginx:1.27 --format '{{json .Config.Labels}}' | jq

Handy fields:

  • .Id — content digest of the image
  • .RepoTags — tags that point at this image
  • .RepoDigests — digest references in registries
  • .Architecture, .Os — target platform (amd64/linux)
  • .Size — total size (in bytes)
  • .Created — when the image was built
  • .Config.* — runtime config (Cmd, Entrypoint, Env, Labels, ExposedPorts, User, WorkingDir, Volumes, Healthcheck)
  • .RootFS.Layers — list of layer digests (in order)
  • .History — build history (Dockerfile-equivalent steps)

docker history

Shows layers from oldest (bottom) to newest (top), with the instruction that created each:

bash
$ docker history nginx:1.27-alpine IMAGE CREATED CREATED BY SIZE COMMENT 4f06b3e2c0c1 2 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemo… 0B <missing> 2 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B <missing> 2 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B <missing> 2 weeks ago /bin/sh -c set -x && addgroup -g 101 -S… 8.94MB <missing> 2 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.27.4 0B <missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 4 weeks ago /bin/sh -c #(nop) ADD file:abcd1234… 7.79MB

Use --no-trunc for full instructions:

bash
docker history --no-trunc nginx:1.27-alpine

<missing> means the intermediate image has no separate ID locally — only the final image does. The instructions are still preserved in metadata.

docker manifest inspect

Shows the registry-side manifest, including multi-arch info:

bash
$ docker manifest inspect nginx:1.27 { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "digest": "sha256:abc123...", "platform": { "architecture": "amd64", "os": "linux" } }, { "digest": "sha256:def456...", "platform": { "architecture": "arm64", "os": "linux" } }, ... ] }

Useful for confirming an image supports your architecture before pulling, or finding the digest for a specific platform.

dive (third-party)

Not built into Docker, but the standard tool for understanding layer contents:

bash
$ dive nginx:1.27-alpine

Opens an interactive TUI:

  • Top panel: layers, with size and command.
  • Bottom panel: file tree of the selected layer (with diffs from previous).
  • Right panel: image details (efficiency score, wasted space).

Invaluable for "why is my image 2 GB?" — you see the exact files contributed by each layer.

Useful patterns

"What CMD will run?"

bash
docker inspect <img> --format '{{.Config.Entrypoint}} {{.Config.Cmd}}'

"What env vars are baked in?"

bash
docker inspect <img> --format '{{json .Config.Env}}' | jq

"What ports does this image listen on?"

bash
docker inspect <img> --format '{{json .Config.ExposedPorts}}' | jq

"What user will the container run as?"

bash
docker inspect <img> --format '{{.Config.User}}' # empty = root (be careful)

"Find the biggest layer"

bash
docker history --format '{{.Size}}\t{{.CreatedBy}}' --no-trunc <img> | sort -h

"What is the content digest?"

bash
docker inspect <img> --format '{{index .RepoDigests 0}}' # myimg@sha256:abc123...

The immutable identifier — use this in production manifests instead of mutable tags.

Common mistakes

Confusing docker inspect for images vs containers

bash
docker inspect myimg # works on image docker inspect mycontainer # works on container # Same command, different objects → different fields available

The schemas differ. Container inspect includes .State (running/exited), .NetworkSettings, .Mounts. Image inspect does not.

Reading <missing> as an error

<missing> in docker history means the intermediate layer ID is not stored locally. The image still works; it is just an artifact of how Docker squashes intermediate metadata. Not a problem.

Forgetting that history shows DOCKERFILE STEPS, not file contents

/bin/sh -c apt-get update && apt-get install ... && rm -rf /var/lib/apt/lists/*

This tells you what the build did, not what files ended up where. For files, use dive or docker save + tar inspection.

Inspecting an image you have not pulled

Local docker inspect requires the image to be in your local cache. For remote images: docker pull first, or use docker manifest inspect (which works against registries).

Real-world usage

  • Pre-deploy verification: docker inspect to confirm CMD, USER, EXPOSE match expectations before promoting.
  • Compliance audit: scan all production images for OCI labels, expected user, no root.
  • Image-size investigation: dive and docker history to track down the biggest layers.
  • Multi-arch verification: docker manifest inspect to ensure your image has both amd64 and arm64 variants before deploying to mixed clusters.
  • Reproducing builds: docker history shows the build commands; cross-reference with the source Dockerfile.

Follow-up questions

Q: What is the difference between Id and RepoDigest?


A: Id is the local image ID — a hash of the config blob. RepoDigest is the registry-side digest (hash of the manifest, used in pull-by-digest). They differ because the manifest hash includes the tag/registry info while the local ID is content-only.

Q: How do I see what is in a specific layer?


A: dive is the easiest. Manually: docker save <img> -o img.tar && tar xf img.tar gives you a directory per layer with their contents.

Q: Can I inspect a running container's image?


A: docker inspect <container> --format '{{.Image}}' returns the image ID; pass that to docker inspect to get image metadata.

Q: What is the difference between docker inspect and docker image inspect?


A: docker inspect is generic (works on images, containers, volumes, networks). docker image inspect is the explicit form for images only. Same output for images.

Q: (Senior) How would you write a CI check that fails if an image has bad metadata?


A: A small script that runs docker inspect and docker history, parses with jq, and asserts: (1) Config.User is not empty (no root containers), (2) required OCI labels present (org.opencontainers.image.source, version), (3) no latest tag in Config.Image, (4) total Size under a threshold, (5) layer count below a max. Wire this into the CI as a gate; fail the pipeline if any assertion breaks. Stronger version uses dive --ci which checks image efficiency scores automatically.

Examples

Quick image audit

bash
$ IMG=nginx:1.27-alpine $ docker inspect $IMG --format \ 'Id={{.Id}} Arch={{.Architecture}} OS={{.Os}} Size={{.Size}} User={{.Config.User}} Cmd={{.Config.Cmd}}' Id=sha256:4f06b3e2c0c1... Arch=amd64 OS=linux Size=54923456 User= Cmd=[nginx -g daemon off;] $ docker history --format 'table {{.Size}}\t{{.CreatedBy}}' --no-trunc $IMG | head SIZE CREATED BY 0B /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"] 0B /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B /bin/sh -c #(nop) EXPOSE 80 8.94MB /bin/sh -c set -x && addgroup -g 101 -S nginx ... 0B /bin/sh -c #(nop) ENV NGINX_VERSION=1.27.4 0B /bin/sh -c #(nop) CMD ["/bin/sh"] 7.79MB /bin/sh -c #(nop) ADD file:abcd...

Four facts at a glance, plus the layer breakdown.

Manifest inspection for multi-arch

bash
$ docker manifest inspect nginx:1.27 | \ jq '.manifests[] | {arch: .platform.architecture, digest: .digest}' { "arch": "amd64", "digest": "sha256:abc..." } { "arch": "arm64", "digest": "sha256:def..." } { "arch": "arm/v7", "digest": "sha256:ghi..." } { "arch": "386", "digest": "sha256:jkl..." }

Confirms which platforms the image supports and gives per-platform digests for pinning.

Find the biggest layer

bash
$ docker history --format '{{.Size}}|{{.CreatedBy}}' --no-trunc node:22-alpine \ | grep -v '^0B|' \ | sort -h \ | tail -5 47.2MB|/bin/sh -c apk add --no-cache python3 ... 42.5MB|/bin/sh -c addgroup -g 1000 node ... 105MB|/bin/sh -c set -ex; ... npm install

The biggest contributor (here, npm install) is your first target if you need to shrink the image.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Comments

No comments yet