Capturing Build Information with BuildKit

Although every Docker image has a manifest — a JSON collection of tags, digital signatures, and configuration details — Docker images can still lack some basic information at build time. Those missing details could be useful to developers. So, how do we fill in the blanks?

In this guide, we’ll highlight a tentpole feature of BuildKit v0.10: new build information-structure generation, from build metadata. This allows you to see all sources (images, Git repositories, and HTTP URLs) and configurations passed on to your build. This information is also embeddable within the image config.

We’ll discuss how to tackle this process, and share some best practices along the way.

Getting Started

While this feature is automatically activated upon updating to BuildKit v0.10, we also recommend using BuildKit’s Dockerfile v1.4 to reliably capture original image names. You can do so by adding the following syntax to your Dockerfile: # syntax=docker/dockerfile:1.4.

Additionally, we recommend creating a new docker-container builder with Buildx that uses the latest stable version of BuildKit. Enter the following CLI command:

$ docker buildx create --use --bootstrap --name mybuilder

Note: to return to the default builder, enter the $ docker buildx use default command.

 

Next, let’s create a basic Dockerfile:

 # syntax=docker/dockerfile:1.4

FROM busybox AS base
ARG foo=baz
RUN echo bar > /foo

FROM alpine:3.15 AS build
COPY --from=base /foo /
RUN echo baz > /bar

FROM scratch
COPY --from=build /bar /
ADD https://raw.githubusercontent.com/moby/moby/master/README.md /

 

We’ll build this image using Buildx v0.8.1 — which comes packaged within the latest version of Docker Desktop (v4.7). The latest Buildx version lets you inspect and use any build information that’s been generated:

$ docker buildx build --build-arg foo=bar --metadata-file metadata.json .

Storing Build Metadata as a File

We’re using the --metadata-file flag, which writes the build result metadata within the metadata.json file. This flag helps retrieve metadata information about your build result — including the digest of your resulting image, and the new containerimage.buildinfo key.

The --metadata-file flag improves upon the previous --iidfile flag, which would only capture the resulting image ID. The following Dockerfile shows the containerimage.buildinfo key in practice:

{
"containerimage.buildinfo": {
"frontend": "dockerfile.v0",
"attrs": {
"build-arg:foo": "bar",
"filename": "Dockerfile",
"source": "docker/dockerfile:1.4"
},
"sources": [
{
"type": "docker-image",
"ref": "docker.io/library/alpine:3.15",
"pin": "sha256:d6d0a0eb4d40ef96f2310ead734848b9c819bb97c9d846385c4aca1767186cd4"
},
{
"type": "docker-image",
"ref": "docker.io/library/busybox:latest",
"pin": "sha256:caa382c432891547782ce7140fb3b7304613d3b0438834dce1cad68896ab110a"
},
{
"type": "http",
"ref": "https://raw.githubusercontent.com/moby/moby/master/README.md",
"pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c"
}
]
},
"containerimage.config.digest": "sha256:cd82085d327d4b41f86212fc372f75682f131b5ce5c0c918dabaa0fbf04ec53f",
"containerimage.descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:6afb8217371adb4b75bd7767d711da74ba226ed868fa5560e01a8961ab150ccb",
"size": 732
},
"containerimage.digest": "sha256:6afb8217371adb4b75bd7767d711da74ba226ed868fa5560e01a8961ab150ccb",
}

 

What’s noteworthy about the structure of this result? The new containerimage.buildinfo key now contains your build information. You’ll also see a host of important field names:

  • frontend defines the BuildKit frontend responsible for the build (we’re building from a Dockerfile above).
  • attrs encompasses the build configuration parameters (e.g. when typing --build-arg).
  • sources defines build sources.

Additionally, each sources entry describes an external source that your Dockerfile used while building the result. However, it’s worth highlighting some other JSON data:

  • type can reference a docker-image for all container images referenced with FROM, or with git using a Git context. Finally, type can reference HTTP URL contexts or remote URLs used by ADD commands.
  • ref is the reference defined in your Dockerfile.
  • pin lets you know which dependency version is installed at any time (digest).

Remember that sources are captured for all of your build stages, and not just for the last stage’s base image that was exported.

Storing Build Metadata as Part of Your Image

Your metadata file isn’t the only transport available. BuildKit also embeds build information within the image config as your image is pushed. This makes your build information portable. Here’s what that push command looks like:

$ docker buildx build --build-arg foo=bar --tag crazymax/buildinfo:latest --push .

You can check the build information for any existing image — while on the latest Buildx version — using the imagetools inspect command:

$ docker buildx imagetools inspect crazymax/buildinfo:latest --format "{{json .BuildInfo}}"

{
"frontend": "dockerfile.v0",
"sources": [
{
"type": "docker-image",
"ref": "docker.io/library/alpine:3.15",
"pin": "sha256:d6d0a0eb4d40ef96f2310ead734848b9c819bb97c9d846385c4aca1767186cd4"
},
{
"type": "docker-image",
"ref": "docker.io/library/busybox:latest",
"pin": "sha256:caa382c432891547782ce7140fb3b7304613d3b0438834dce1cad68896ab110a"
},
{
"type": "http",
"ref": "https://raw.githubusercontent.com/moby/moby/master/README.md",
"pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c"
}
]
}

 

Unlike with your metadata-file results, build attributes aren’t automatically available within the image config. Attribute inclusion is currently an opt-in configuration to avoid troublesome leaks. Developers occasionally use these attributes as secrets, which isn’t ideal from a security standpoint. We recommend using --mount=type=secret instead.

To add build attributes to your embedded image config, use the image output attribute, buildinfo-attrs:

$ docker buildx build \
--build-arg foo=bar \
--output=type=image,name=crazymax/buildinfo:latest,push=true,buildinfo-attrs=true .

 

Alternatively, you can use the Buildx BUILDKIT_INLINE_BUILDINFO_ATTRS build argument:

$ docker buildx build \
--build-arg BUILDKIT_INLINE_BUILDINFO_ATTRS=1 \
--build-arg foo=bar \
--tag crazymax/buildinfo:latest \
--push .

That’s it! You may now review any newly-generated build dependencies stemming from your image builds.

What’s next?

Transparency is important. Always aim to make your Docker images more self-descriptive, decipherable, reproducible, and visible. In this case, we’ve made it easier to uncover any inputs used while building an image. Additionally, you might compare images updated with security patches to pinned versions of your build sources. That lets you know if your image is up-to-date or safe to use.

This is one important step in our Secure Software Supply Chain (SSSC) journey, and towards better build reproducibility. More information about reproducibility is also available within our BuildKit repo.

However, we want to go even further. Docker is bringing SBOMs to all container images via BuildKit. We want to get our development community involved in this effort to bolster BuildKit — and take that next major step towards higher image-level transparency.

Are you interested in learning more about BuildKit? Our latest BuildKit release has shipped with other useful features — like those showcased in Tonis Tiigi’s blog post. If you’ve been clamoring for improved remote cache support and rapid image rebase, it’s well worth a read!

Feedback

0 thoughts on "Capturing Build Information with BuildKit"