This article takes a detailed look at BuildKit‘s attestation feature, one of several options for creating and verifying attestations for Docker images.
Docker Image signing and attestation series
This article is part of a multi-part series:
- Overview: explains concepts and tool landscape, provides high-level recommendations
- Docker Image signing with Notation: detailed analysis of the Notation tool and its signatures
- Docker Image signing and attestation with Cosign: detailed analysis of Cosign, for image signatures and attestations
- Docker Image attestation with GitHub attestations: detailed analysis of GitHub attestations and its pros&cons
- (This article) Docker Image attestations with BuildKit: detailed analysis of BuildKit’s attestations
Introduction
BuildKit, the image builder used under the hood by “docker build
”, can create attestations for the Docker image that it builds, uploading these attestations to the image registry (as JSON manifests) as part of the manifest of the image.
This attestation feature is documented in the BuildKit docs here, but you can also access it via buildx, which is a (thin) BuildKit abstraction layer that is part of the docker
CLI (via docker CLI’s plug-in mechanism). Buildx simplifies the creation, usage, and management of BuildKit instances, and provides simplified CLI arguments (compared to buildctl
, which is the tool you would use when calling BuildKit directly, as a stand-alone builder, see also this blog post for more details).
The Buildx attestation-related documentation can be found here. Its content is the same as the BuildKit attestation documentation, only the names and values of the CLI arguments are slightly different.
Supported attestation types
As of February 2025, BuildKit supports two kinds of attestations:
- SBOMs: a Software Bill Of Material is a list of all software components found in your image. After building the image, BuildKit runs an SBOM generator tool to generate an SPDX-JSON-formatted SBOM. The SBOM generator is configurable. By default, BuildKit uses Syft, but Docker Scout is another option. See the docs for how to configure it.
- Build provenance: defines how, when, and where the image was created. BuildKit uses the SLSA provenance schema as the concrete file format. It includes information such as:
- Build parameters (e.g. Docker
--build-arg
) - Build timestamps
- Version control metadata for your build sources (e.g. Git repo URL and commit hash)
- Build dependencies with their immutable checksums. E.g. base images or external URLs used by the build
- Descriptions of all build steps (the statements in your
Dockerfile
)
- Build parameters (e.g. Docker
Attestation deep dive
Let’s look at a concrete example of how the manifests look like. You can also look at them interactively here. They were generated by this GitHub actions workflow, where the build-push-action was configured to create SBOM and build provenance attestations.
The image manifest looks like this:
As you can see, it’s an image index manifest. Image index manifests are created whenever you build a multi-platform image. The GitHub action built and pushed two images: one compiled for the ARM64 CPU architecture, and one for AMD64. This allows you to run this image on either CPU architecture.
These two images are reflected by the first two entries in the manifests
array. The last 2 manifests
entries are the attestations. BuildKit added a separate attestation for each platform image (rather than just creating one attestation that applies to both architectures) because the attestations contain checksums and the AMD64 and ARM64 binaries will have different checksums. The “unknown” values in the platform
key ensure that a container runtime does not accidentally try to pull and run them as regular images.
Let’s look at the content of one of the attestation entries (fafc15c3e2e…
):
We see an image manifest whose layers
object lists the two attestations BuildKit created (one for the SBOM in SPDX format, one for the SLSA-build provenance). Looking at these more closely:
In the Build provenance attestation, line 675 is the content of the original Dockerfile
, encoded as Base64.
Lack of signatures
A closer analysis of these manifests demonstrates that the generated attestations are not cryptographically signed! That means that anyone who possesses (or stole) access tokens to push images can create a tampered attestation (e.g. an incomplete SBOM or incorrect SLSA build provenance) and attach it to an image. You, the consumer, would be unable to detect such modifications! That means that BuildKit’s attestations are rather useless in practice!
Docker does not mention this problem. On the contrary, they are misleading you by using phrases like “we use in-toto attestations” in many places in the docs. This is misleading for two reasons:
- 1) Most sources in the literature define the word “attestation” to include the ability of cryptographic verification.
- 2) If someone claims to use “in-toto attestations”, you would expect them to use the entire specification, that is, that they create Bundles, which is the “outermost” layer that includes the cryptographic signatures. Instead, BuildKit only creates and uploads the two innermost layers of in-toto attestations, Statements and Predicates. These inner layers don’t concern themselves with cryptographic signatures!
Automated verification
There are no options to automatically verify BuildKit’s attestations! Docker didn’t build any tooling, and neither did anyone else.
You can merely manually inspect the attestations, or extract them to files and write custom code that analyzes these files, e.g. docker buildx imagetools inspect <myorg>/<myimage> --format
"{{ json .SBOM.SPDX }}"
to get the SBOM, or docker buildx imagetools inspect <namespace>/<image>:<version>--format "{{ json .Provenance.SLSA }}"
to get the build attestation.
Adoption
You might wonder who in the community already creates BuildKit attestations.
As it turns out, official Docker images come with both attestation types. Also, every public GitHub repo that builds images using the docker/build-push-action has build provenance attestations (unless the repo’s workflow explicitly disables it), but not SBOM attestations. Note that running “docker buildx build
” does not create attestations by default. You need to provide the additional parameters “--sbom=true --provenance=true
“, as explained in the documentation.
Conclusion
For me, the usefulness of creating attestations hinges on whether you can automatically and cryptographically verify them.
Unfortunately, Docker made the “mistake” of inventing their own standard (that defines how attestations are attached to the image) rather than using existing standards, such as specifications of the Sigstore or Notary Project. Hence, it is not surprising that no other tools in the ecosystem (e.g., Kubernetes Operators like Kyverno) offer features for automated verification of BuildKit’s attestations. Docker/BuildKit also has no command to verify the attestations for correctness, e.g. comparing image layer hashes with hashes from the build provenance manifest. You can merely inspect the attestations, manually. And if you look at a build attestation JSON, large parts of it are incomprehensible to a normal person.
The only automation-related real-world use case I currently see for BuildKit’s attestations is using the SBOM attestation: somewhere in your pipelines or deployments you (hopefully) regularly create vulnerability reports of your images. The typical workflow of a vulnerability scanner is to create an SBOM of an image and then check every contained component against a vulnerability database. Since BuildKit has already built the SBOM, you can speed up the process: First, extract BuildKit’s SBOM into a file, then tell the vulnerability scanner to use that SBOM file. This workflow is documented by Grype here and Trivy here. Docker’s own Docker Scout tool can even fetch the BuildKit-attached SBOMs automatically, so you don’t need to explicitly download the SBOM to a file, see the documentation.
The other problem with BuildKit’s attestations is the lack of cryptographic signatures. You can work around this, though, by using another tool to sign the image index manifest, e.g. Cosign. I discuss Cosign in depth in another article.
You can probably tell that my opinion of Docker/BuildKit attestations is not particularly positive. So I might as well vent about Docker’s track record when it comes to cryptographically signing images (just a few facts, form your own opinion):
- In 2015, Docker Inc. created Docker Content Trust (docs), a.k.a. “Notary V1”. It never took off, in part because it has various issues (see e.g. here and here). It’s also rather complicated to understand and use (unless you are an IT security expert) – just look at all the different keys you need to generate and handle.
- In this blog from October 2023, Docker expressed its plans to use OpenPubKey to sign their Official Images. As of 03/2025, they did not implement it.