Trivy can identify vulnerabilities in your base image and all its parent layers, not just the immediate image you’re scanning.

Let’s see it in action. Imagine you have a Dockerfile that looks like this:

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y --no-install-recommends \
    nginx \
    && rm -rf /var/lib/apt/lists/*

COPY index.html /var/www/html/

You build this image and then scan it with Trivy:

docker build -t my-nginx-app .
trivy image my-nginx-app

The output will show vulnerabilities found in my-nginx-app. But what about ubuntu:22.04? And what if ubuntu:22.04 was built on top of another image? Trivy traverses the image’s ancestry by default.

Here’s a snippet of what you might see:

my-nginx-app (Ubuntu 22.04)
=====================
Total: 50
CVE-2023-12345  High    Ubuntu 22.04 ...
CVE-2023-67890  Medium  Nginx ...
...

ubuntu:22.04 (Ubuntu 22.04)
=====================
Total: 45
CVE-2023-11111  High    Ubuntu 22.04 ...
...

Trivy’s --is-ancestor flag is actually the default behavior when scanning images. It’s designed to answer the question: "If I use this image, what are the inherent risks inherited from its lineage?" This is crucial because a vulnerability in a base image, even one you didn’t directly install, can still compromise your application. Trivy helps you surface these "hidden" risks.

When Trivy scans an image, it first identifies the base image(s) it’s built upon. It does this by inspecting the image’s layers and looking for metadata that indicates an FROM instruction from a parent image. For official images like Ubuntu or Alpine, this metadata is usually well-defined. For custom base images, Trivy might infer it based on common package managers or OS signatures.

Once the parent image is identified, Trivy then recursively scans that parent image. This process continues up the chain until it reaches an image that has no identifiable parent (often a minimal base like scratch or a very early debian or alpine image) or until it hits a configured depth limit (though the default is to go all the way up).

The output separates findings by the specific image in the lineage where the vulnerability was detected. This allows you to pinpoint whether a vulnerability is in your application’s direct dependencies, the base OS you chose, or even further up the chain in a foundational image that many others might depend on.

The levers you control here are primarily the image you choose to scan and the filters you apply. You can scan a specific base image directly (trivy image ubuntu:22.04) to understand its inherent risk profile, or scan your application image to see the cumulative risk.

Consider a scenario where you’re using a custom-built base image that your team maintains. You might think it’s secure because you only install a few packages. However, if that custom base image was built on an older, unpatched official image, Trivy would reveal vulnerabilities originating from that older ancestor.

To explicitly control the depth of the ancestry scan, you can use the --image-history-depth flag. For instance, trivy image --image-history-depth 1 my-nginx-app would only scan my-nginx-app itself and not its parent ubuntu:22.04. The default is effectively unlimited depth for ancestry.

The most surprising aspect for many is how deeply Trivy can trace these dependencies, often revealing issues in images that are many layers removed from the one you’re directly interacting with. It’s not just about the packages you RUN apt-get install or RUN apk add — it’s about the entire foundation upon which your image is built.

The next logical step after understanding your base image’s vulnerabilities is to look at how Trivy can help in a CI/CD pipeline.

Want structured learning?

Take the full Trivy course →