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:
# 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}}' | jqHandy 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:
$ 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.79MBUse --no-trunc for full instructions:
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:
$ 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:
$ dive nginx:1.27-alpineOpens 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?"
docker inspect <img> --format '{{.Config.Entrypoint}} {{.Config.Cmd}}'"What env vars are baked in?"
docker inspect <img> --format '{{json .Config.Env}}' | jq"What ports does this image listen on?"
docker inspect <img> --format '{{json .Config.ExposedPorts}}' | jq"What user will the container run as?"
docker inspect <img> --format '{{.Config.User}}'
# empty = root (be careful)"Find the biggest layer"
docker history --format '{{.Size}}\t{{.CreatedBy}}' --no-trunc <img> | sort -h"What is the content digest?"
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
docker inspect myimg # works on image
docker inspect mycontainer # works on container
# Same command, different objects → different fields availableThe 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 inspectto confirm CMD, USER, EXPOSE match expectations before promoting. - Compliance audit: scan all production images for OCI labels, expected user, no root.
- Image-size investigation:
diveanddocker historyto track down the biggest layers. - Multi-arch verification:
docker manifest inspectto ensure your image has both amd64 and arm64 variants before deploying to mixed clusters. - Reproducing builds:
docker historyshows 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
$ 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
$ 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
$ 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 installThe biggest contributor (here, npm install) is your first target if you need to shrink the image.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet