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 aDockerfile
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 adocker-image
for all container images referenced withFROM
, or withgit
using a Git context. Finally,type
can reference HTTP URL contexts or remote URLs used byADD
commands.ref
is the reference defined in yourDockerfile
.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!