Inspecting an Image
An image is a dependency you are about to trust with your application, and it can be read like one. docker image inspect, docker history, and a glance at its layers and size tell you what base it's built on, what command it runs, what ports it exposes, and — the part most people skip — whether anything sensitive was baked into its history. Reading an image before you run it is a basic supply-chain habit, no different from skimming a library's source before you import it.
Driftwood depends on postgres:16 and nginx:1.27-alpine, and this topic reads both the way you'd read a third-party package: what they execute by default, what they expose, what they declare as volumes, and whether their build hid anything you'd rather know about.
docker image inspect
docker image inspect dumps the image config as JSON: the default CMD and ENTRYPOINT, the ENV variables, exposed ports, the working directory, declared volumes, labels, and the architecture. Reading it tells you what a container will do before you start one — what process runs, what environment it inherits, what it expects to listen on.
$ docker image inspect postgres:16 --format '{{json .Config}}' | jq
{
"Entrypoint": ["docker-entrypoint.sh"],
"Cmd": ["postgres"],
"Env": ["PG_MAJOR=16", "PGDATA=/var/lib/postgresql/data", "..."],
"ExposedPorts": { "5432/tcp": {} },
"Volumes": { "/var/lib/postgresql/data": {} },
"WorkingDir": "",
"Labels": null
}
For postgres:16 the readout is concrete: it runs docker-entrypoint.sh postgres, exposes 5432/tcp, and declares /var/lib/postgresql/data as a volume. That last line matters — it means an anonymous volume appears automatically unless you mount your own, which is exactly the kind of surprise you want to know about before wiring the image into Compose.
docker history
docker history lists each layer with the Dockerfile instruction that created it and its size. It is how the image was built, read backwards — and it is where secrets hide. A value passed via ARG or ENV, or a file added in one layer and "deleted" in a later one, shows up in the history even though it isn't visible in a running container.
$ docker history nginx:1.27-alpine IMAGE CREATED CREATED BY SIZE b4e3f1a2c9d0 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B <missing> 2 weeks ago EXPOSE 80 0B <missing> 2 weeks ago RUN /bin/sh -c set -x && apk add --no-cache … 24.1MB <missing> 2 weeks ago COPY docker-entrypoint.sh / 2.6KB <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:… in / 7.4MB
The per-layer instructions read like a condensed Dockerfile. If a build had done RUN echo $SECRET > /tmp/key && … && rm /tmp/key, the key's bytes would still be in that layer regardless of the later rm — which is precisely why build secrets need BuildKit's --secret mount rather than an ARG, a point the Dockerfile chapter develops.
docker image inspectCMD/ENTRYPOINT, ENV, exposed ports, volumes. Tells you what a container will do before you start one.docker historyReading Size and Layers
Per-layer sizes show where the weight actually is. An image's total is just the sum of its layers, so a single 400 MB layer is usually one uncleaned package cache or one fat COPY — and docker history names the exact instruction that produced it. That turns image-shrinking from guesswork into targeting: you fix the instruction that costs the bytes, not whichever one you noticed first.
This is the diagnostic that directs the size work in the Dockerfile chapter. Before optimizing anything, you read the history, find the heaviest layer, and look at the instruction above it — the bloat almost always has a single, nameable cause.
Labels and Provenance
Well-built images carry OCI labels — source repository, revision, build date — recorded in the config. Their presence is a quick signal of how carefully an image was produced: an image that declares where its source lives and which commit built it is one you can trace, while one with Labels: null tells you nothing about its origin. It's a thirty-second provenance check before you depend on something.
Inspecting Driftwood's Dependencies
Reading postgres:16 and nginx:1.27-alpine this way surfaces exactly what you need before composing them. Postgres runs postgres under its entrypoint, listens on 5432, and declares a data volume; nginx runs nginx -g 'daemon off;', exposes 80, and copies in an entrypoint script. Those are the defaults, ports, and volumes you'll wire into Compose later — and knowing them now means no surprises about anonymous volumes or unreachable services then.
The habit generalizes past these two. Any image you're about to run, you inspect first: its entrypoint so you know what executes, its history so you know what's hidden, its exposed ports and volumes so you know how it'll behave. It costs a minute and prevents a class of "why is this doing that" debugging later.
- Running an unfamiliar image without inspecting its
ENTRYPOINT/CMD, env, and history — you don't know what it executes, what it exposes, or what's hidden in its layers until something surprises you. - Passing a secret via
--build-argand assuming it's gone from the image —docker historyand the layers still hold it; build secrets need BuildKit's--secretmount, covered in the Dockerfile chapter. - Treating image size as a black box rather than reading per-layer sizes — without
docker historyyou optimize blind instead of fixing the one instruction that produced the bloated layer. - Ignoring whether an image declares volumes or exposes ports before composing it — then being surprised by an anonymous volume or an unreachable service that the config would have warned you about.
- Inspect any third-party image's config and history before depending on it, the same way you'd skim a library's source before importing it.
- Use
docker historyto audit your own images for accidentally baked-in secrets or oversized layers before pushing them to a registry. - Read per-layer sizes to aim image-shrinking work at the instruction that actually costs the bytes, rather than guessing.
- Prefer images that carry provenance labels (source, revision) and add them to your own builds so every image is traceable to the commit that produced it.
Knowledge Check
What does docker image inspect reveal that docker history does not?
- The image's config — default command, env, exposed ports, and volumes — whereas history shows the build instructions
- The live, continuously streaming logs of every single container that happens to be running from this particular image right now
- A direct way to rewrite the entrypoint and default command of the image in place without a rebuild
- Whether you are currently authenticated to the remote registry that stores and serves this image
Why does a secret passed via ARG or ENV survive in an image even after a later layer "deletes" it?
- It is recorded in the build history and the layer that added it, and a later deletion only masks it rather than removing the bytes
- ENV values are encrypted at build time, but the matching decryption key is shipped right alongside them in the very same image, so they decrypt trivially
- The daemon silently re-injects the secret back into the container at run time from an internal cache it never clears
- Rebuilding the image from the Dockerfile is the only operation that re-adds the secret, so its presence cannot be avoided
How do per-layer sizes help you shrink an image?
- They locate the heaviest layer and the instruction that produced it, so you fix the bloat at its source
- Simply reading the per-layer sizes automatically compresses the single largest layer in order to reclaim disk space
- They let the daemon selectively skip loading the largest layers at container start in order to save host memory
- They reveal exactly which tag the image was originally pushed under, so you can re-tag it under a smaller one
What should you check on a third-party image before running it?
- Its entrypoint and command, env, exposed ports, declared volumes, and history for anything hidden in its layers
- Only its total download and pull count and star rating on Docker Hub, which on its own conclusively confirms the image is entirely safe to run
- Only how recently its tag was last pushed, which on its own guarantees the image has no secrets baked in
- Only whether its Docker Hub listing page has a clean, well-formatted readme and polished documentation
You got correct