Do You Know What’s In Your Docker Image? Buildpacks Do.
You can’t patch a vulnerability you don’t know you have. That’s why knowing what’s inside a Docker image is the first step toward making sure it’s secure. Fortunately, any image that’s built with Cloud Native Buildpacks contains metadata you can use to determine not only what the image contains, but also what’s in each layer and how the image was created.
Any image you create with the Pack CLI using the pack build
command can also be inspected with the inspect-image
command, like this:
$ pack inspect-image ekcasey/myimage
Inspecting image: ekcasey/myimageREMOTE:Stack: io.buildpacks.stacks.bionicBase Image:
Reference: index.docker.io/cloudfoundry/run@sha256:8de6ef35195a6ebb4875cfb2ab5dfb4334c7bef85425318739a6c6bfc4edb202
Top Layer: sha256:9335faa48c88fc9f7f831f86e41ba979fa402bc715671b11d63c4f3eb4f69458
…Run Images:
cloudfoundry/run:base-cnbBuildpacks:
ID VERSION
org.cloudfoundry.openjdk v1.0.48
org.cloudfoundry.jvmapplication v1.0.66
org.cloudfoundry.springboot v1.0.88LOCAL:
...
This example output tells us that the image was built with three buildpacks. It also tells us the stack ID and the tag of the run-image used to create the final image. Included with this information is the reference for the “Top Layer” of the stack, which you can use to separate the base image from the layers the buildpack created.
This information is what Pack uses to perform its rebase
command, where the layers created by the buildpack are used to create a new image with an updated stack — without ever running a build.
The metadata on the image also contains information about what’s inside each layer. We can view this by providing the --bom
option, which emits a Bill-of-Materials for the image. This information is derived from the build plan created by the buildpacks. Because this information is stored in the config layer of the image it can be read cheaply without pulling the entire image from the registry.
$ pack inspect-image ekcasey/myimage --bom | jq .remote
[
{
"buildpack": {
"id": "org.cloudfoundry.openjdk",
"version": "v1.0.48"
},
"metadata": {
"licenses": [
{
"type": "GPL-2.0 WITH Classpath-exception-2.0",
"uri": "https://openjdk.java.net/legal/gplv2+ce.html"
}
],
"name": "OpenJDK JRE",
"sha256": "2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b",
"uri": "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.5%2B10/OpenJDK11U-jre_x64_linux_hotspot_11.0.5_10.tar.gz"
},
"name": "openjdk-jre",
"version": "11.0.5"
},
...
In this entry we can see that the image contains OpenJDK JRE version 11.0.5. We can also see the license associated with this JRE and the URI it was pulled from.
Another example Bill-of-Materials entry contains information including the classpath and installed dependencies
$ pack inspect-image ekcasey/myimage --bom | jq .remote
...
{
"buildpack": {
"id": "org.cloudfoundry.springboot",
"version": "v1.0.88"
},
"metadata": {
"classes": "BOOT-INF/classes/",
"classpath": [
"/workspace/BOOT-INF/classes",
"/workspace/BOOT-INF/lib/attoparser-2.0.5.RELEASE.jar",
"/workspace/BOOT-INF/lib/classmate-1.4.0.jar",
"/workspace/BOOT-INF/lib/hibernate-validator-6.0.18.Final.jar",
"/workspace/BOOT-INF/lib/jackson-annotations-2.9.10.jar",
...
],
"lib": "BOOT-INF/lib/",
"start-class": "io.buildpacks.example.sample.SampleApplication",
"version": "2.1.10.RELEASE"
},
"name": "spring-boot",
"version": ""
},
...
This information can be used by hand or more likely with automation to ensure compliance, scan for vulnerable dependencies, or to generate files for open source licensing disclosure.
Buildpacks use structured data to report the exact contents of an image instead of relying on external tools that glean what they can from the filesystem. This makes them well suited for organizations that have strict compliance or security requirements without forcing developers to compromise on their tooling. Buildpacks achieve a balance between ease-of-use and operability by separating developers from concerns like generating a Bill-of-Materials.