HackBlue

Tools · Secrets and dependency scanning · 05 / 06

Trivy: gate container images and Terraform in one pipeline step

Use Trivy to scan a built image for fixable high-severity vulnerabilities, scan Terraform for misconfigurations, and fail CI only on findings the team has agreed to block.

11 min read · practice only with authorization

One tool, several targets

Trivy scans container images, filesystems, git repositories, and infrastructure-as-code. For images it reads OS packages and language lockfiles. For Terraform, Kubernetes manifests, and Dockerfiles it applies misconfiguration checks. Running both in the same CI step keeps the configuration in one place.

Base image choice usually matters more than any single upgrade. Scanning two candidates side by side, such as a full Debian image and its slim or distroless counterpart, often shows the smaller one carrying a fraction of the packages and findings. trivy config on the Dockerfile adds checks such as running as root or a missing HEALTHCHECK.

Snippet
trivy image --severity HIGH,CRITICAL -q python:3.12 | grep Total
trivy image --severity HIGH,CRITICAL -q python:3.12-slim | grep Total
trivy config --severity HIGH,CRITICAL Dockerfile

An image gate that teams can live with

--severity limits results; --ignore-unfixed hides vulnerabilities with no released fix, because the team cannot act on them by upgrading; --exit-code 1 turns remaining results into a failed job.

Snippet
trivy image --severity HIGH,CRITICAL --ignore-unfixed \
  --exit-code 1 ghcr.io/hackblue-lab/api:2026.10.2

trivy image --format cyclonedx -o sbom.cdx.json ghcr.io/hackblue-lab/api:2026.10.2

The vulnerability table

Installed and fixed versions tell the developer exactly which upgrade closes each row.

Snippet
ghcr.io/hackblue-lab/api:2026.10.2 (debian 12.7)
Total: 2 (HIGH: 1, CRITICAL: 1)

┌──────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┐
│ Library  │ Vulnerability  │ Severity │ Status │ Installed Version │ Fixed Version │
├──────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┤
│ libexpat1│ CVE-2024-45491 │ CRITICAL │ fixed  │ 2.5.0-1           │ 2.5.0-1+deb12u1│
│ urllib3  │ CVE-2024-37891 │ HIGH     │ fixed  │ 2.0.7             │ 2.2.2         │
└──────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┘

Worked case: a Terraform bucket and an accepted risk

trivy config on the infra folder flags an S3 bucket without public access block settings. The module gains aws_s3_bucket_public_access_block with all four settings true. A second finding, a missing access log on a bucket that only holds build caches, is accepted: it goes into .trivyignore with the check id, a reason, and a review date.

Snippet
trivy config --severity HIGH,CRITICAL --exit-code 1 infra/

# .trivyignore
# build cache bucket, no customer data; review 2027-04-01
AVD-AWS-0089

Diagram

A Trivy step in CI

  1. Build

    Produce the image the pipeline will ship.

    Command
    docker build -t ghcr.io/hackblue-lab/api:2026.10.2 .
  2. Scan image

    Block fixable high and critical issues.

    Command
    trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 <image>
  3. Scan config

    Check Terraform before apply.

    Command
    trivy config --exit-code 1 infra/
  4. Record

    Keep the SBOM with the release.

    Command
    trivy image --format cyclonedx -o sbom.cdx.json <image>

Check yourself

1. Why do many teams pass --ignore-unfixed in a blocking gate?

2. Which Trivy command checks Terraform files for misconfigurations?

3. What should a .trivyignore entry carry besides the check id?