Safeguard
Container Security

Scanning Docker Images in CI/CD Pipelines

Scanning a container after it deploys is an incident report. Scanning it in the pipeline is a one-line diff. Here is how to gate builds on image scans without drowning developers in false positives.

Marcus Chen
Cloud Security Engineer
5 min read

A vulnerability caught in a pull request is a version bump. The same vulnerability caught by a runtime alert three weeks after deployment is an incident review, a hotfix, and possibly a customer notification. IBM's Cost of a Data Breach research has consistently shown that issues found and contained earlier cost dramatically less, and the same economics apply to container images: the cheapest place to fix a bad image is the pipeline that built it. Yet most teams either scan too late or scan so noisily that developers tune the results out. The NVD enrichment backlog that began in February 2024 made the noise worse, leaving thousands of CVEs published without the severity metadata teams rely on to triage. This guide covers how to scan Docker images in CI/CD so the gate is fast, the findings are trustworthy, and the build only fails when it should.

Step 1: Scan on every build, not on a weekly cron

A base image that was clean on Monday can have a critical CVE disclosed by Wednesday. Scan the image you just built, every time you build it, and fail the job on new, fixable, critical findings.

# Build, then gate the build on the scan result
docker build -t myapp:$GIT_SHA .
safeguard scan --image myapp:$GIT_SHA \
  --fail-on critical \
  --only-fixable

--only-fixable avoids failing a build for a CVE that has no patch available yet, which would otherwise leave developers with a red pipeline and nothing to do.

Step 2: Wire the gate into your pipeline

Add the scan as a required job before the push-to-registry step. A GitHub Actions example:

jobs:
  build-and-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t myapp:${{ github.sha }} .
      - name: Scan image
        run: |
          safeguard scan --image myapp:${{ github.sha }} \
            --fail-on critical --only-fixable

Step 3: Generate an SBOM as a build artifact

Produce the SBOM at build time, when the image's contents are unambiguous, and store it alongside the image. That turns "are we affected by this new CVE" into a lookup instead of a re-scan.

safeguard sbom --image myapp:$GIT_SHA \
  --format cyclonedx \
  --output sbom.json

Step 4: Re-scan images already in the registry

New CVEs are disclosed daily against images you built weeks ago and have not rebuilt. Schedule a re-scan of the registry so a six-month-old "approved" image cannot quietly accumulate criticals.

# Nightly re-scan of the deployed tag
safeguard scan --registry registry.example.com/myapp --tag prod

Step 5: Prioritize by reachability, not raw CVSS

A typical image scan returns hundreds of findings. FIRST's EPSS data shows only a small fraction of published CVEs are ever exploited in the wild, and CISA's KEV catalog lists the ones under active attack. Rank findings by whether the vulnerable code is reachable and whether it is being exploited, not by CVSS alone, or developers will learn to ignore the gate.

Where to scan across the pipeline

StageWhat it catchesGate action
Pre-commit / localobvious issues before pushwarn
Pull requestnew CVEs in the diffblock merge on new criticals
Buildfull image + dependency treefail on fixable criticals
Registry (scheduled)newly disclosed CVEsticket / alert
Admission controlunsigned or over-threshold imagesreject deploy

How Safeguard scans your images

Safeguard runs as a single step in your pipeline and does the whole job: it parses filesystem layers and language manifests to inventory every component, matches them against vulnerability data enriched independently of the NVD backlog, and runs reachability analysis so the gate fails on findings that are actually exploitable from your code — not on the majority that sit in unreachable paths. It emits a CycloneDX or SPDX SBOM as a build artifact, re-scans registry images on a schedule, and opens an auto-fix pull request with the minimal bump when a fix exists, so a failed gate comes with a ready remedy. Drop it into any pipeline with the Safeguard CLI, gate pull requests with Secure Containers, and cover application dependencies with software composition analysis. See how the prioritized gate differs from a raw scanner in the Safeguard vs Trivy comparison, or browse the full comparison hub.

Frequently Asked Questions

What severity threshold should fail a build?

Start by failing only on new, fixable critical findings, then tighten to high once the pipeline is stable and the queue is manageable. Failing on every finding, or on unfixable ones, trains developers to bypass the gate, which is worse than a looser threshold they respect.

Should scanning block the pipeline or just warn?

Warn in early stages and on informational findings; block at the build gate on fixable criticals with a reachable path. The point of blocking is to stop a genuinely exploitable image from reaching a registry, not to make every CVE a merge blocker.

How do I stop the scanner from failing on vulnerabilities with no fix?

Use a fixable-only filter so the gate fails only when a patched version exists. Track unfixable findings separately as accepted risk with an expiry, so they are reviewed again rather than ignored forever.

Is scanning in CI enough, or do I still need runtime scanning?

CI scanning is the highest-leverage control because it is the cheapest place to fix, but it is not the only one. Re-scan the registry for newly disclosed CVEs, and verify images at admission control, so a vulnerability disclosed after build still gets caught before it runs.

Want a pipeline gate that fails only on what matters? Connect a repository at app.safeguard.sh/register, and read the CI/CD integration steps in the documentation at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.