Almost every team runs a container scanner. Far fewer get value from it. The failure mode is depressingly consistent: a scanner is bolted onto the pipeline, it returns three hundred findings on the first image, nobody can tell which twelve matter, and within a month developers have learned to treat the red output as background noise and merge anyway. The scanner is working exactly as designed — the program around it is not. This guide is about that program: where in the lifecycle to scan, how to prioritize what comes back, and how to gate deploys without becoming the team everyone routes around.
What a container scanner actually does
A container image scanner unpacks each layer, enumerates the OS packages and language dependencies present, and matches their names and versions against vulnerability databases like the NVD, distro security trackers, and ecosystem advisories. That is a genuinely useful inventory-and-match operation, but it has two well-known blind spots: it tells you a vulnerable package is present, not whether the vulnerable code is reachable, and it depends entirely on the quality and freshness of the databases it consults. Both blind spots got sharper after NIST's National Vulnerability Database fell behind on enrichment in 2024, leaving many CVEs published without the metadata teams rely on to triage.
Scan at three points, not one
Scanning is not a single gate — it is a habit you apply at each stage where an image can change or age.
Build time (in CI). Scan every image as it is built, before it merges. A CVE caught here is a one-line version bump in a pull request. Fail the build on new criticals that have a fix available.
Registry (continuously). An image that was clean on Monday can have a critical CVE disclosed against it on Wednesday without a single byte changing. Re-scan images already sitting in your registry on a schedule so a six-month-old "approved" image does not quietly become your biggest exposure.
Admission (at deploy). Verify at the moment of scheduling that the image meets policy and carries a valid signature, so nothing sneaks in through a side door or a manually pushed tag.
A minimal CI gate looks like this:
# .github/workflows/scan.yml
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Scan and fail on fixable criticals
run: |
safeguard scan image app:${{ github.sha }} \
--fail-on critical \
--only-fixed \
--reachable-only
The --only-fixed flag matters as much as the severity threshold: failing a build on a critical with no available patch just blocks your team on something they cannot fix.
The real problem is prioritization, not detection
A typical enterprise image scan returns hundreds of findings, but a small fraction of published CVEs are ever observed being exploited in the wild — FIRST's Exploit Prediction Scoring System (EPSS) consistently shows most CVEs never get weaponized. Ranking findings by CVSS alone, which measures theoretical severity, tells you nothing about whether your service ever runs the affected code. Effective triage combines three signals:
- Reachability — is the vulnerable function actually called from your application's code paths, or is it dead weight in the dependency tree?
- Presence in the final image — many CVEs live in build-time-only dependencies that never ship in the runtime layer.
- Exploit maturity — is there a public proof-of-concept, or is it listed in CISA's Known Exploited Vulnerabilities (KEV) catalog?
A "medium" CVE in a library you call on every request outranks a "critical" in a parser your service never touches. Teams that adopt reachability-based triage routinely cut their actionable queue by an order of magnitude.
Handle the vulnerabilities you cannot fix
Not every real finding has a patch, and pretending otherwise is how a gate turns into a permanent exception list. When a vulnerability is genuinely unreachable, or a fix is not yet available, record that decision as data rather than silence. The VEX (Vulnerability Exploitability eXchange) format exists for exactly this: it lets you assert, in a machine-readable document that travels with the image, that a given CVE is "not affected" and why. A documented, expiring suppression is auditable and reviewable; a scanner someone quietly configured to ignore a CVE is neither. Set suppressions to expire so they are re-examined when a patch eventually lands, and require a reason and an owner for each — the discipline that keeps a triage program honest is treating every exception as a claim you would defend to an auditor.
Scanning approaches compared
| Approach | Strength | Watch out for |
|---|---|---|
| OS package scan | Fast, broad coverage | Misses app-layer and language deps |
| Language dependency (SCA) | Catches transitive libs | Noisy without reachability |
| Secret scanning | Finds baked-in credentials | Needs layer-level, not just source |
| Reachability analysis | Cuts false positives sharply | Requires call-graph awareness |
| Signature verification | Proves provenance | Says nothing about vuln content |
The lesson from the table: no single technique is sufficient. A credible program layers package scanning, dependency analysis, and reachability, then verifies signatures at admission so provenance and content are both checked.
How Safeguard helps
Safeguard is built for the prioritization problem, not just the detection one. Container security scanning enumerates OS and language packages, secrets, and misconfigurations across build, registry, and admission, so you cover all three scan points with one tool instead of stitching together three. Griffin AI then runs reachability analysis against your actual call graph, so the findings that reach a ticket are the ones with a real exploitation path — and it opens an auto-fix pull request when a reachable issue has a known patch. The same engine powers our software composition analysis for the language layer, and the Safeguard CLI drops the exact gate shown above into any pipeline. Curious how reachability-first scanning differs from database-matching tools? Compare Safeguard vs Trivy and Safeguard vs Anchore.
Stop drowning your developers in findings they will learn to ignore. Create a free Safeguard account to try reachability-based scanning, or read the documentation to wire it into CI.