A Docker or OCI image is composed of layers. Some of the layers are created during a build process such as following instructions in a Dockerfile. But many of the layers will come from previously built images. These images likely come from a container team at your organization, or maybe build directly on images from a Linux distribution vendor. In some cases this chain could be many images deep as various teams add standard software or configuration.
Docker uses the FROM
clause to denote an image to use as a basis for building a new image. The image provided in this clause is known by Docker as the Parent Image, but is commonly referred to as the Base Image. This chain of images built from other images using the FROM
clause is known as an Image’s ancestry.
Note Docker defines Base Image as an image with a
FROM SCRATCH
clause. Anchore does NOT follow this definition, instead following the more common usage where Base Image refers to the image that a given image was built from.
Example Ancestry
The following is an example of an image with multiple ancestors
A base distro image, for example debian:10
FROM scratch
...
A framework container image from that debian image, for example a node.js image let’s call mynode:latest
FROM debian:10
# Install nodejs
The application image itself built from the framework container, let’s call it myapp:v1
FROM mynode:latest
COPY ./app /
...
These dockerfiles generate the following ancestry graph:
graph debian:10-->|parent of|mynode:latest mynode:latest-->|parent of|myapp:v1
Where debian:10
is the parent of mynode:latest
which is the parent of myapp:v1
Anchore compares the layer digests of images that it knows about to determine an images ancestry. This ensures that the exact image used to build a new image is identified.
Given our above example, we may see the following layers for each image. Note that each subsequent image is a superset of the previous images layers
block-beta columns 5 block:debian_block columns 1 debian["debian:10"] style debian stroke-width:0px debian_layer1["sha256:abc"] debian_layer2["sha256:def"] space space end space block:mynode_block columns 1 mynode_name["mynode:latest"] style mynode_name stroke-width:0px mynode_layer1["sha256:abc"] mynode_layer2["sha256:def"] mynode_layer3["sha256:123"] space end space block:myapp_block columns 1 myapp_name["myapp:v1"] style myapp_name stroke-width:0px myapp_layer1["sha256:abc"] myapp_layer2["sha256:def"] myapp_layer3["sha256:123"] myapp_layer4["sha256:456"] end mynode_name -- "FROM" --> debian debian_layer1 --> mynode_layer1 mynode_layer1 --> debian_layer1 debian_layer2 --> mynode_layer2 mynode_layer2 --> debian_layer2 myapp_name -- "FROM" --> mynode_name mynode_layer1 --> myapp_layer1 myapp_layer1 --> mynode_layer1 mynode_layer2 --> myapp_layer2 myapp_layer2 --> mynode_layer2 mynode_layer3 --> myapp_layer3 myapp_layer3 --> mynode_layer3
Ancestry within Anchore
Anchore automatically calculates an image’s ancestry as images are scanned. This works by comparing the layer digests of each image to calculate the entire chain of images that produced a given image. The entire ancestry can be retrieved for an image through the GET /v2/images/{image_digest}/ancestors
API. See the API docs for more information on the specifics.
Base Image
It is often useful to compare an image with another image in its ancestry. For example to filter out vulnerabilities that are present in a “golden image” from a platform team and only showing vulnerabilities introduced by the application being built on the “golden image”.
Controlling the Base Image
Users can control which ancestor is chosen as the base image by marking the desired image(s) with a special annotation anchore.user/marked_base_image
. The annotation should be set to a value of true
, otherwise it will be ignored. This annotation is currently restricted to users in the “admin” account.
If an image with this annotation should no longer be considered a Base Image than you must update the annotation to false
, as it is not currently possible to remove annotations.
Usage of this annotation when calculating the Base Image can be disabled by setting services.policy_engine.enable_user_base_image
to false in the configuration file (see deployment specific docs for configuring this setting).
Anchorectl Example
You can add an image with this annotation using AnchoreCTL with the following:
anchorectl image add anchore/test_images:ancestor-base -w --annotation "anchore.user/marked_base_image=true"
If an image should no longer be considered a Base Image you can update the annotation with:
anchorectl image add anchore/test_images:ancestor-base --annotation "anchore.user/marked_base_image=false"
Calculating the Base Image
Anchore will automatically calculate the Base Image from an image’s ancestry using the closest ancestor. From our example above, the Base Image for myapp:v1
is mynode:latest
.
The first ancestor with this annotation will be used as the Base Image, if no ancestors have this annotation than it will fall back to using the closest ancestor (the Parent Image).
The rules for determining the Base Image are encoded in this diagram
graph start([start])-->image image[image] image-->first_parent_exists first_parent_exists{Does this image have a parent?} first_parent_exists-->|No|no_base_image first_parent_exists-->|yes|first_parent_image first_parent_image[Parent Image] first_parent_image-->config config{User Base Annotations Enabled in configuration?} config-->|No|base_image config-->|yes|check_parent check_parent{Parent has anchore.user/marked_base_image: true annotation} check_parent-->|No|parent_exists parent_exists{Does the parent image have a parent?} parent_exists-->|Yes|parent_image parent_image[/Move to next Parent Image/] parent_image-->check_parent parent_exists-->|No|no_base_image check_parent-->|Yes|base_image base_image([Found Base Image]) no_base_image([No Base Image Exists])
Using the Base Image
The Policy evaluation and Vuln Scan APIs have an optional base_digest
parameter that is used to provide comparison data between two images. These APIs can be used in conjunction with the ancestry API to perform comparisons to the Base Image so that application developers can focus on results in their direct control. As of Enterprise v5.7.0, a special value auto
can also be specified for this parameter to have the system automatically determine which image to use in the comparison based on the above rules.
To read more about the base comparison features, jump to
In addition to these user facing APIs, a few parts of the system utilize the Ancestry information.
- The Ancestry Policy Gate uses the Base Image rules to determine which image to evaluate against
- Reporting uses the Base Image to calculate the “Inherited From Base” column for vulnerabilities
- The UI displays the Base Image and uses it for Policy Evaluations and Vulnerability Scans
Additional notes about ancestor calculations
An image B is only a child of Image A if All of the layers of Image A are present in Image B.
For example, mypython
and mynode
represent two different language runtime images built from a debian base. These two images are not ancestors of each other because the layers in mypython:latest
are not a superset of the layers in mynode:latest
, nor the other way around.
block-beta columns 3 block:mypython_block columns 1 mypython_name["mypython:latest"] style mypython_name stroke-width:0px mypython_layer1["sha256:abc"] mypython_layer2["sha256:def"] mypython_layer3["sha256:456"] end space block:mynode_block columns 1 mynode_name["mynode:latest"] style mynode_name stroke-width:0px mynode_layer1["sha256:abc"] mynode_layer2["sha256:def"] mynode_layer3["sha256:123"] end mypython_layer1 --> mynode_layer1 mynode_layer1 --> mypython_layer1 mypython_layer2 --> mynode_layer2 mynode_layer2 --> mypython_layer2 mypython_layer3 -- "X" --> mynode_layer3
But these images could be based on a 3rd image which is made up of the 2 layers that they share. If Anchore knows about this 3rd image it would show up as an ancestor for both mypython
and mynode
.
The Anchore UI would identify the debian:10
image as an ancestor of the mypython:latest
and the mynode:latest
images. But we do not currently expose child ancestors, so we would not show the children of the debian:10
image.
block-beta columns 5 block:mypython_block columns 1 mypython_name["mypython:latest"] style mypython_name stroke-width:0px mypython_layer1["sha256:abc"] mypython_layer2["sha256:def"] mypython_layer4["sha256:456"] end space block:debian_block columns 1 debian["debian:10"] style debian stroke-width:0px debian_layer1["sha256:abc"] debian_layer2["sha256:def"] space end space block:mynode_block columns 1 mynode_name["mynode:latest"] style mynode_name stroke-width:0px mynode_layer1["sha256:abc"] mynode_layer2["sha256:def"] mynode_layer3["sha256:123"] end mynode_name -- "FROM" --> debian debian_layer1 --> mynode_layer1 mynode_layer1 --> debian_layer1 debian_layer2 --> mynode_layer2 mynode_layer2 --> debian_layer2 mypython_name -- "FROM" --> debian debian_layer1 --> mypython_layer1 mypython_layer1 --> debian_layer1 debian_layer2 --> mypython_layer2 mypython_layer2 --> debian_layer2