17.1.3.2 Routine Image Scans
A focused guide to Routine Image Scans, connecting core concepts with practical Docker and container operations.
Routine image scans go beyond a one-time check performed at build time, addressing the reality that an image's actual vulnerability status changes after it is built, as new vulnerabilities are continuously disclosed against the exact same, unchanged package versions already present inside it, which means scanning needs to be a recurring, scheduled activity applied to both newly built and already-deployed images alike.
Why a build-time-only scan is insufficient
A scan performed at build time reflects the vulnerability database's state at that exact moment; a new vulnerability disclosed the following week against a dependency already baked into a deployed image produces no automatic, retroactive alert unless that image is scanned again after the fact:
trivy image my-api:1.4.2
Total: 0 (CRITICAL: 0)
This clean result is accurate only as of the moment the scan ran; running the identical command against the identical, unchanged image weeks later could produce a very different result, entirely due to newly disclosed vulnerabilities rather than anything that changed about the image itself.
Integrating scanning as a CI pipeline gate
Embedding a vulnerability scan directly into the build pipeline, configured to fail the build if vulnerabilities above a defined severity threshold are found, prevents a newly built image with known, serious vulnerabilities from ever reaching a registry or deployment in the first place:
scan:
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL my-api:$CI_COMMIT_TAG
Setting the severity threshold deliberately, rather than failing on every single finding regardless of severity, balances catching genuinely actionable issues against avoiding excessive build failures for lower-severity findings that may not warrant blocking a release on their own.
Scheduling recurring scans against already-deployed images
Beyond the build-time gate, a separate, scheduled process specifically re-scanning images already running in production catches newly disclosed vulnerabilities affecting content that was already deployed before that disclosure occurred:
0 6 * * * trivy image --severity HIGH,CRITICAL registry.example.com/my-api:1.4.2 \
--format json -o /var/log/scans/my-api-$(date +%Y%m%d).json
This recurring scan is the actual mechanism that closes the build-time-only gap, since it continues checking the same, unchanged image content against an ever-updating vulnerability database long after the original build-time scan's result has become outdated.
Establishing a severity threshold and response policy
A scan that produces findings with no defined process for what happens next provides limited practical value; establishing explicit policy, which severities require immediate action, which can be tracked and addressed on a routine schedule, and who is responsible for acting on a given finding, turns scanning from a passive activity into one that actually drives remediation:
CRITICAL: patch or mitigate within 24 hours
HIGH: patch within 7 days
MEDIUM: address in next routine maintenance cycle
LOW: track, no mandatory action required
Documenting this policy explicitly, and holding the team accountable to it, ensures scan results actually translate into action on a predictable timeline rather than accumulating as an ever-growing, unaddressed backlog of findings.
Handling false positives and accepted risk
Not every flagged finding represents a genuinely exploitable risk in the specific context it appears, and a documented, deliberate process for marking a specific finding as a false positive or an accepted risk, rather than either ignoring it silently or blocking every release over it indefinitely, keeps the scanning process credible and sustainable:
vulnerabilities:
ignore:
- id: CVE-2023-12345
reason: "Not exploitable in this context; affected code path is never invoked"
reviewed_by: "security-team"
expires: "2024-12-31"
Including an expiration date on any such exception ensures it is periodically revisited rather than becoming a permanent, unreviewed exclusion that might no longer be accurate as the application or its usage of the affected dependency evolves over time.
Tracking scan results over time
Retaining a historical record of scan results, rather than only acting on the most recent one, allows tracking trends, whether vulnerability counts are generally decreasing as patches are applied, or accumulating faster than they are being addressed, which provides a useful, higher-level signal about whether the overall remediation process is actually keeping pace:
trivy image --format json registry.example.com/my-api:1.4.2 > scans/my-api-$(date +%Y%m%d).json
Building a simple dashboard or report from this accumulated historical data surfaces whether the organization's vulnerability management process is genuinely effective over time, rather than relying on a single, isolated scan result as the only available signal.
Choosing and combining scanning tools
Several mature, widely used scanning tools exist, Trivy, Grype, Docker Scout, and various commercial offerings, each with somewhat different vulnerability database sources and detection capabilities, and running more than one occasionally, or at least periodically cross-checking results between them, can catch a finding one tool's specific database happens to miss:
trivy image my-api:1.4.2
grype my-api:1.4.2
For most organizations, a single, well-integrated tool used consistently and routinely provides considerably more value than occasionally running multiple tools inconsistently; the choice of which specific tool matters less than ensuring whichever one is chosen is actually run routinely and its results are actually acted upon.
Alerting on newly discovered findings specifically
Rather than requiring someone to manually review a full scan report on every run, alerting specifically on new findings, those not present in the previous scan of the same image, focuses attention on what has actually changed rather than requiring a full re-review of an entire, potentially large and largely unchanged report every single time:
diff <(jq '.Results[].Vulnerabilities[].VulnerabilityID' previous-scan.json) \
<(jq '.Results[].Vulnerabilities[].VulnerabilityID' current-scan.json)
This kind of differential comparison, surfacing only genuinely new findings since the last scan, keeps the routine scanning process from becoming noise that gets ignored simply because reviewing a full report every time is too time-consuming to sustain.
Common mistakes
- Scanning only at build time and never re-scanning already-deployed images, missing vulnerabilities disclosed after the original build-time scan ran.
- Not establishing an explicit severity threshold and response timeline policy, leaving scan findings to accumulate as an unaddressed backlog with no defined process for acting on them.
- Either ignoring false positives silently or blocking every release over them indefinitely, rather than using a documented, time-limited exception process.
- Not retaining historical scan results, losing the ability to track whether vulnerability remediation is genuinely keeping pace over time.
- Requiring a full manual review of every scan report rather than alerting specifically on newly introduced findings since the previous scan.
Routine image scans deliver their actual value through recurring, scheduled execution against both newly built and already-deployed images, paired with an explicit severity and response policy, a documented exception process for genuine false positives, and historical tracking that reveals whether the overall vulnerability remediation process is actually working over time.