In my environment, I treat SBOM generation not as a compliance checkbox but as a live data source for dependency governance. Using Trivy inside GitLab CI lets me produce SBOM JSON during the build phase, then feed that same output into later stages for CVE scanning and policy decisions. This avoids duplicate scans and keeps the SBOM as a single source of truth.
Why SBOM JSON Matters Beyond Scanning
I used to run Trivy just for CVE lists, but the real value came when I started capturing the SBOM in SPDX or CycloneDX JSON format. That file becomes inventory: I can compare it against approved dependency lists, detect unexpected transitive libraries, or feed it into internal tools that enforce version policies. In one case, it caught a deprecated logging library pulled in by a base image update — something the CVE scan missed because it had no known vulns yet.
GitLab CI Setup: Build, SBOM, Then Scan
Here’s how my .gitlab-ci.yml structures the flow:
stages:
- build
- sbom
- scan
variables:
TRIVY_FORMAT: "spdx-json"
SBOM_ARTIFACT: "sbom-${CI_COMMIT_SHORT_SHA}.spdx.json"
build_app:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
generate_sbom:
stage: sbom
image: aquasec/trivy:latest
script:
- trivy image --format $TRIVY_FORMAT --output $SBOM_ARTIFACT $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
paths:
- $SBOM_ARTIFACT
expire_in: 1 hour
scan_vulns:
stage: scan
image: aquasec/trivy:latest
dependencies:
- generate_sbom
script:
- trivy image --format table --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
allow_failure: false
policy_check:
stage: scan
image: python:3.11-slim
dependencies:
- generate_sbom
script:
- pip install jq
- |
COUNT=$(jq '.packages | length' $SBOM_ARTIFACT)
if [ $COUNT -gt 50 ]; then
echo "Too many dependencies: $COUNT"
exit 1
fi
The SBOM artifact flows from the sbom stage into both scan and policy_check. This means I’m not pulling the image twice or regenerating the inventory — just reusing the JSON.
Using SBOM JSON for Dependency Governance
Once I have the SBOM, I treat it like a manifest. In the policy_check stage above, I count packages to enforce a soft limit on dependency sprawl. But I’ve also used it to:
- Compare against a whitelist of allowed libraries (using jq to extract package names and versions)
- Detect when a base image introduces new packages not in the previous SBOM (via diff between artifacts)
- Trigger automated ticket creation if an LGPL-licensed component appears in a proprietary service
These aren’t hypotheticals — I’ve implemented similar checks in internal tooling after seeing how easy it is to extract structured data from Trivy’s SBOM output. As I mentioned before in my post on SLSA provenance, having machine-readable build metadata changes how you think about supply chain risk.
Pitfalls and Tuning Tips
Trivy’s SBOM generation isn’t instant. For large images, it can add 20–40 seconds to the job. I mitigate this by:
- Using
--skip-filesto skip scanning non-essential paths if I only want OS/package SBOM - Caching the Trivy image via GitLab CI’s
imagepull policy (it’s usually already cached) - Avoiding SBOM generation on every branch — I limit it to merge requests and mainline builds via
rules
Also, watch the output format. SPDX JSON is verbose; CycloneDX is lighter. I switched to CycloneDX for policy checks because it’s faster to parse, but keep SPDX for archival since it’s more widely accepted in compliance tools.
Conclusion
Generating SBOM JSON with Trivy in GitLab CI isn’t just about checking a box for SBOM requirements — it’s about creating a reusable artifact that powers both security scanning and operational governance. By decoupling SBOM creation from CVE scanning and feeding the same JSON into multiple stages, I get faster pipelines, better dependency insight, and fewer surprises when policies change.
If you’re already running Trivy in CI, start capturing the SBOM. Then ask: what else could I do with this inventory beyond finding CVEs?
Cover image: HD Wallpapers · CC0 (Openverse / kamu malı) · https://stocksnap.io/photo/light-abstract-V9L6XXK3LB
