skip to content

Using `docker buildx imagetools inspect`, how do you read an image's provenance and SBOM attestations?

level: middleimportance: nice to knowfreq 32%

answer

  1. Not `docker image inspect`
  2. A buildx subcommand that talks to the registry
  3. A Go template flag decodes the payload
  4. One flag prints the index verbatim
  5. Multi-platform results are keyed by platform

basics

~10 s

docker buildx imagetools inspect <ref> --format '{{ json .Provenance }}' and the same with .SBOM decode the attestation payloads straight from the registry. --raw prints the underlying index JSON. No local pull is needed.

solid answer

~40 s

`docker buildx imagetools inspect` talks to the registry directly, so it can show attestations for an image that was never pulled to the machine. Three forms matter. `--raw` prints the raw index or manifest JSON — this is where you see the extra attestation manifests with their `vnd.docker.reference.type=attestation-manifest` annotation and their `unknown/unknown` platform. `--format '{{ json .Provenance }}'` fetches and decodes the provenance attestation into readable JSON; `--format '{{ json .SBOM }}'` does the same for the SBOM. On a multi-platform image the result is keyed by platform, so you index into it for the architecture you care about. Note what this is **not**: reading a claim is not verifying it. `docker image inspect` and `docker history` will never show attestations, because they only look at the image's own config and layers.

code

bash · 12 lines
bash
IMAGE=registry.example.com/billing/invoice-worker:2.7.3

# the shape of the pushed index, including attestation manifests
docker buildx imagetools inspect "$IMAGE" --raw

# the decoded attestation payloads
docker buildx imagetools inspect "$IMAGE" --format '{{ json .Provenance }}'
docker buildx imagetools inspect "$IMAGE" --format '{{ json .SBOM }}'

# one platform of a multi-platform image
docker buildx imagetools inspect "$IMAGE" \
  --format '{{ json (index .Provenance "linux/arm64") }}'

go deeper

for a junior

Remember that attestations are read with a buildx subcommand against the registry reference, not with docker image inspect. Knowing the command exists and that it needs no local pull is the expected level of recall.

for a middle

Be able to demonstrate the forms: --raw for the index structure, and a --format '{{ json .Provenance }}' template to decode the payload. Explain why the image's own config and history can never carry this data.

for a senior

Show the operational uses — inspecting by digest rather than tag, indexing per platform, and piping the JSON into a pipeline assertion — and be clear that reading a claim is not verifying it.

for a principal

Frame where this belongs in an estate: a read command is a debugging and triage tool, and any control built on it needs a signed claim and an enforcement point, otherwise you have automated the reading of an attacker-writable file.

### Why a separate command exists `docker image inspect` reads the local image store and reports the image **config**: entrypoint, environment, labels, layer digests. Attestations are not in the config and not in a layer — they are sibling manifests in the index — so that command structurally cannot show them, and neither can `docker history`. `docker buildx imagetools inspect` is the registry-facing tool: it resolves a reference over the distribution API and can walk the whole index without pulling the image at all. That distinction matters in practice, because the machine asking the question is often a CI runner or a laptop that has no business downloading a 400 MB PHP-FPM image just to read a build record. ### The three useful forms **1. The shape of what was pushed.** ```bash docker buildx imagetools inspect \ registry.example.com/billing/invoice-worker:2.7.3 --raw ``` This prints the index JSON verbatim. You are looking for entries with `"platform": {"os": "unknown", "architecture": "unknown"}` and the annotations `vnd.docker.reference.type: attestation-manifest` plus `vnd.docker.reference.digest`, which names the platform manifest each attestation describes. If those entries are absent, the build did not attach anything — or attached it and then lost it on the way to the registry. **2. The provenance record.** ```bash docker buildx imagetools inspect \ registry.example.com/billing/invoice-worker:2.7.3 \ --format '{{ json .Provenance }}' ``` The `--format` flag takes a Go template. Buildx fetches the attestation blob, unwraps the in-toto envelope and hands you the decoded predicate, so you can read build timestamps, the builder that ran it, the source context, and — if the build used `mode=max` — the full build definition and the build arguments. `{{ json .SBOM }}` does the same for the component inventory, and `{{ json . }}` dumps everything the command knows about the reference, including `.Manifest` and `.Image`. **3. One platform at a time.** For a multi-platform image there is one attestation set per platform, and `.Provenance` is therefore a map keyed by platform string. Ask for the one you deploy: ```bash docker buildx imagetools inspect "$IMAGE" \ --format '{{ json (index .Provenance "linux/arm64") }}' ``` Getting this wrong is the usual first stumble — people print `.Provenance` on a two-platform image, see a JSON object keyed by something that is not the predicate they expected, and conclude the attestation is malformed. ### Pin the reference you inspect Inspect by digest, not by tag, whenever the answer is going to be used for anything. A tag can be re-pointed between the moment you read the record and the moment the invoice-rendering worker is deployed, and then you have read a build record for bytes nobody is running: ```bash docker buildx imagetools inspect \ registry.example.com/billing/invoice-worker@sha256:4c91a7e0d5b3... \ --format '{{ json .Provenance }}' ``` ### Reading is not verifying This is the point an interviewer is usually probing. `imagetools inspect` performs no cryptographic check on what it prints. It fetches a blob the registry served and decodes it. If the attestation is unsigned — which is what a plain `docker buildx build --push` produces — then anyone who can push to that repository can replace the index with one whose provenance says whatever they like, and this command will faithfully print the lie. Treat the output as *what the artefact claims about itself*, useful for answering questions and for feeding a check, and keep the question of who signs and who enforces separate from the question of how you read it. ### Practical uses that justify learning it - **Triage after an incident.** Given only a running image digest, one command tells you which source revision and which builder produced it, without a rebuild and without guessing from tags. - **A CI assertion.** Because the output is JSON, a pipeline step can pipe it into a JSON processor and fail the job when, say, the recorded source reference is not the release branch. That is a cheap gate that costs one registry round trip. - **Debugging the attach path.** When a team believes it is producing attestations and is not, `--raw` settles it in seconds: either the extra manifests are in the index or they never arrived. - **Auditing what leaked.** After switching a build to `mode=max`, `{{ json .Provenance }}` is how you check whether a build argument you did not want published is now sitting in a public registry. ### What to remember One command, three shapes: `--raw` for structure, `{{ json .Provenance }}` and `{{ json .SBOM }}` for content, and an index into the map when the image is multi-platform. It reads from the registry, needs no pull, and proves nothing on its own.

  • Why can't `docker image inspect` show you the same information?
    `docker image inspect` reports the image config for an image in the local store — entrypoint, env, labels, layer digests. Attestations are separate manifests in the index and touch neither the config nor the layers, so there is nothing for that command to report. `docker history` is blind to them for the same reason.
  • Does `imagetools inspect` verify the attestation it prints?
    No. It fetches the blob the registry serves and decodes it. A plain buildx push stores the attestation unsigned, so anyone with push access to the repository could replace it, and the command would print the replacement without complaint. Use it to read and to feed a check, never as the check itself.

saying these in an interview costs you the question

  • Reaches for `docker image inspect` to find the SBOM
  • Says the image must be pulled locally first
  • Expects the attestation to appear in `docker history`
  • Thinks `imagetools inspect` validates or verifies the claim
  • Assumes one provenance blob covers all platforms

context