SBOM attestations

Software Bill of Materials (SBOM) attestations describe what software artifacts an image contains, and artifacts used to create the image. Metadata included in an SBOM for describing software artifacts may include:

  • Name of the artifact
  • Version
  • License type
  • Authors
  • Unique package identifier

There are benefits to indexing contents of an image during the build, as opposed to scanning a final image. When scanning happens as part of the build, you're able to detect software you use to build the image, that may not show up in the final image.

The SBOMs generated by BuildKit follow the SPDX standard. SBOMs attach to the final image as a JSON-encoded SPDX document, using the format defined by the in-toto SPDX predicate.

Create SBOM attestations

To create an SBOM attestation, pass the --attest type=sbom option to the docker buildx build command:

$ docker buildx build --tag <namespace>/<image>:<version> \
    --attest type=sbom --push .

Alternatively, you can use the shorthand --sbom=true option instead of --attest type=sbom.

For an example on how to add SBOM attestations with GitHub Actions, see Add attestations with GitHub Actions.

Verify SBOM attestations

Always validate the generated SBOM for your image before you push your image to a registry.

To validate, you can build the image using the local exporter. Building with the local exporter saves the build result to your local filesystem instead of creating an image. Attestations are written to a JSON file in the root directory of your export.

$ docker buildx build \
  --sbom=true \
  --output type=local,dest=out .

The SBOM file appears in the root directory of the output, named sbom.spdx.json:

$ ls -1 ./out | grep sbom
sbom.spdx.json

Arguments

By default, BuildKit only scans the final stage of an image. The resulting SBOM doesn't include build-time dependencies installed in earlier stages, or that exist in the build context. This may cause you to overlook vulnerabilities in those dependencies, which could impact the security of your final build artifacts.

For instance, you might use multi-stage builds, with a FROM scratch stanza for your final stage to achieve a smaller image size.

FROM alpine AS build
# build the software ...

FROM scratch
COPY --from=build /path/to/bin /bin
ENTRYPOINT [ "/bin" ]

Scanning the resulting image built using this Dockerfile example would not reveal build-time dependencies used in the build stage.

To include build-time dependencies from your Dockerfile, you can set the build arguments BUILDKIT_SBOM_SCAN_CONTEXT and BUILDKIT_SBOM_SCAN_STAGE. This expands the scanning scope to include the build context and additional stages.

You can set the arguments as global arguments (after declaring the Dockerfile syntax directive, before the first FROM command) or individually in each stage. If set globally, the value propagates to each stage in the Dockerfile.

The BUILDKIT_SBOM_SCAN_CONTEXT and BUILDKIT_SBOM_SCAN_STAGE build arguments are special values. You can't perform variable substitution using these arguments, and you can't set them using environment variables from within the Dockerfile. The only way to set these values is using explicit ARG command in the Dockerfile.

Scan build context

To scan the build context, set the BUILDKIT_SBOM_SCAN_CONTEXT to true.

# syntax=docker/dockerfile:1
ARG BUILDKIT_SBOM_SCAN_CONTEXT=true
FROM alpine AS build
# ...

You can use the --build-arg CLI option to override the value specified in the Dockerfile.

$ docker buildx build --tag <image>:<version> \
    --attest type=sbom \
    --build-arg BUILDKIT_SBOM_SCAN_CONTEXT=false .

Note that passing the option as a CLI argument only, without having declared it using ARG in the Dockerfile, will have no effect. You must specify the ARG in the Dockerfile, whereby you can override the context scanning behavior using --build-arg.

Scan stages

To scan more than just the final stage, set the BUILDKIT_SBOM_SCAN_STAGE argument to true, either globally or in the specific stages that you want to scan. The following table demonstrates the different possible settings for this argument.

ValueDescription
BUILDKIT_SBOM_SCAN_STAGE=trueEnables scanning for the current stage
BUILDKIT_SBOM_SCAN_STAGE=falseDisables scanning for the current stage
BUILDKIT_SBOM_SCAN_STAGE=base,binEnables scanning for the stages named base and bin

Only stages that are built will be scanned. Stages that aren't dependencies of the target stage won't be built, or scanned.

The following Dockerfile example uses multi-stage builds to build a static website with Hugo.

# syntax=docker/dockerfile:1
FROM alpine as hugo
ARG BUILDKIT_SBOM_SCAN_STAGE=true
WORKDIR /src
COPY <<config.yml ./
title: My Hugo website
config.yml
RUN apk add --upgrade hugo && hugo

FROM scratch
COPY --from=hugo /src/public /

Setting ARG BUILDKIT_SBOM_SCAN_STAGE=true in the hugo stage ensures that the final SBOM includes the information that Alpine Linux and Hugo were used to create the website.

Building this image with the local exporter creates two JSON files:

$ docker buildx build \
  --sbom=true \
  --output type=local,dest=out .
$ ls -1 out | grep sbom
sbom-hugo.spdx.json
sbom.spdx.json

Inspecting SBOMs

To explore created SBOMs exported through the image exporter, you can use imagetools inspect.

Using the --format option, you can specify a template for the output. All SBOM-related data is available under the .SBOM attribute. For example, to get the raw contents of an SBOM in SPDX format:

$ docker buildx imagetools inspect <namespace>/<image>:<version> \
    --format "{{ json .SBOM.SPDX }}"
{
  "SPDXID": "SPDXRef-DOCUMENT",
  ...
}

You can also construct more complex expressions using the full functionality of Go templates. For example, you can list all the installed packages and their version identifiers:

$ docker buildx imagetools inspect <namespace>/<image>:<version> \
    --format "{{ range .SBOM.SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}"
adduser@3.118ubuntu2
apt@2.0.9
base-files@11ubuntu5.6
base-passwd@3.5.47
...

SBOM generator

BuildKit generates the SBOM using a scanner plugin. By default, it uses is the BuildKit Syft scanner plugin. This plugin is built on top of Anchore's Syft, an open source tool for generating an SBOM.

You can select a different plugin to use with the generator option, specifying an image that implements the BuildKit SBOM scanner protocol.

$ docker buildx build --attest type=sbom,generator=<image> .

Tip

The Docker Scout SBOM generator is available. See Docker Scout SBOMs.

SBOM attestation example

The following JSON example shows what an SBOM attestation might look like.

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://spdx.dev/Document",
  "subject": [
    {
      "name": "pkg:docker/<registry>/<image>@<tag/digest>?platform=<platform>",
      "digest": {
        "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862"
      }
    }
  ],
  "predicate": {
    "SPDXID": "SPDXRef-DOCUMENT",
    "creationInfo": {
      "created": "2022-12-16T15:27:25.517047753Z",
      "creators": ["Organization: Anchore, Inc", "Tool: syft-v0.60.3"],
      "licenseListVersion": "3.18"
    },
    "dataLicense": "CC0-1.0",
    "documentNamespace": "https://anchore.com/syft/dir/run/src/core/sbom-cba61a72-fa95-4b60-b63f-03169eac25ca",
    "name": "/run/src/core/sbom",
    "packages": [
      {
        "SPDXID": "SPDXRef-b074348b8f56ea64",
        "downloadLocation": "NOASSERTION",
        "externalRefs": [
          {
            "referenceCategory": "SECURITY",
            "referenceLocator": "cpe:2.3:a:org:repo:\\(devel\\):*:*:*:*:*:*:*",
            "referenceType": "cpe23Type"
          },
          {
            "referenceCategory": "PACKAGE_MANAGER",
            "referenceLocator": "pkg:golang/github.com/org/repo@(devel)",
            "referenceType": "purl"
          }
        ],
        "filesAnalyzed": false,
        "licenseConcluded": "NONE",
        "licenseDeclared": "NONE",
        "name": "github.com/org/repo",
        "sourceInfo": "acquired package info from go module information: bin/server",
        "versionInfo": "(devel)"
      },
      {
        "SPDXID": "SPDXRef-1b96f57f8fed62d8",
        "checksums": [
          {
            "algorithm": "SHA256",
            "checksumValue": "0c13f1f3c1636491f716c2027c301f21f9dbed7c4a2185461ba94e3e58443408"
          }
        ],
        "downloadLocation": "NOASSERTION",
        "externalRefs": [
          {
            "referenceCategory": "SECURITY",
            "referenceLocator": "cpe:2.3:a:go-chi:chi\\/v5:v5.0.0:*:*:*:*:*:*:*",
            "referenceType": "cpe23Type"
          },
          {
            "referenceCategory": "SECURITY",
            "referenceLocator": "cpe:2.3:a:go_chi:chi\\/v5:v5.0.0:*:*:*:*:*:*:*",
            "referenceType": "cpe23Type"
          },
          {
            "referenceCategory": "SECURITY",
            "referenceLocator": "cpe:2.3:a:go:chi\\/v5:v5.0.0:*:*:*:*:*:*:*",
            "referenceType": "cpe23Type"
          },
          {
            "referenceCategory": "PACKAGE_MANAGER",
            "referenceLocator": "pkg:golang/github.com/go-chi/chi/v5@v5.0.0",
            "referenceType": "purl"
          }
        ],
        "filesAnalyzed": false,
        "licenseConcluded": "NONE",
        "licenseDeclared": "NONE",
        "name": "github.com/go-chi/chi/v5",
        "sourceInfo": "acquired package info from go module information: bin/server",
        "versionInfo": "v5.0.0"
      }
    ],
    "relationships": [
      {
        "relatedSpdxElement": "SPDXRef-1b96f57f8fed62d8",
        "relationshipType": "CONTAINS",
        "spdxElementId": "SPDXRef-043f7360d3c66bc31ba45388f16423aa58693289126421b71d884145f8837fe1"
      },
      {
        "relatedSpdxElement": "SPDXRef-b074348b8f56ea64",
        "relationshipType": "CONTAINS",
        "spdxElementId": "SPDXRef-043f7360d3c66bc31ba45388f16423aa58693289126421b71d884145f8837fe1"
      }
    ],
    "spdxVersion": "SPDX-2.2"
  }
}