Container image scanning
Trivy: report all, fail on critical.
If you build container images, the image itself is an artifact to scan — its base OS packages and language libraries carry CVEs just like your dependency tree. Container scanning (Trivy is the standard, and GitLab ships a built-in template) inspects the built image before it ships. The workflow is: build the image, scan it, and only push it to the registry if it passes the gate — so a vulnerable image never reaches a cluster.
container-scan:stage: testimage: { name: aquasec/trivy:0.53.0, entrypoint: [""] }variables: { TRIVY_CACHE_DIR: .trivycache/ } # cache the vuln DB (~40MB)cache: { paths: [.trivycache/] }script:- trivy image --exit-code 0 --severity LOW,MEDIUM,HIGH "$IMAGE" # report all- trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed "$IMAGE" # gate
The two-pass pattern, again
The same shape recurs across scanners because it works: one pass reports every severity with exit code 0 (nothing fails, full visibility), a second pass fails the build with exit code 1 on only the severities you would actually page for, plus --ignore-unfixed so you never block on a CVE with no patch. A scanner that blocks every merge gets turned off; one that blocks only actionable criticals stays on.
Keep scans fast, or they get skipped
A scan that adds three minutes to every pipeline is a scan developers route around. Two moves keep it fast: cache Trivy’s vulnerability database (about 40MB) between runs so it is not re-downloaded each time, and pin the scanner image version so the job is reproducible and the DB cache stays valid. A sub-30-second scan is one nobody minds. And of course, the smaller the image (distroless, from the container course), the less there is to scan and fewer CVEs to triage.