finding vulnerabilities before they get deployed
A Docker image is made up of many layers of code someone else wrote: the base distribution, system-wide packages, a language runtime, and application dependencies. Any of those layers can carry known vulnerabilities without a developer ever having written a single line of that code themselves. Automated image scanning in the CI/CD pipeline surfaces those vulnerabilities before an image is even built toward production, preventing known gaps from quietly going live.
Table of Contents
- 1. Why image scanning has to be a fixed part of the pipeline
- 2. How vulnerability scanners analyze an image
- 3. Trivy versus Grype
- 4. A CI gate that blocks the build on critical CVEs
- 5. Choosing sensible severity thresholds
- 6. Handling vulnerabilities in base images you do not control
- 7. SBOM as a complement to a pure scan
- 8. Managing false positives and exceptions cleanly
- 9. Scanning in the registry versus in the CI pipeline run
- 10. Summary
- 11. FAQ
1. Why image scanning has to be a fixed part of the pipeline
A production Docker image is rarely pure application code. It almost always contains a base distribution such as Debian or Alpine, plus system packages, a language runtime like PHP or Node.js, and a long list of libraries installed through a package manager. Every one of those components is maintained independently of your own team, which means new vulnerabilities can be publicly disclosed at any time even though nothing in your own application code has changed.
Without systematic scanning, the only way to catch such gaps is manually checking security advisories, which in practice rarely happens consistently. Automated scanning turns that check into a fixed, repeatable step of every pipeline run, making sure a new critical CVE does not go unnoticed for weeks, or forever.
2. How vulnerability scanners analyze an image
A scanner like Trivy or Grype takes a container image apart layer by layer and identifies the packages installed inside it using their metadata, such as Debian's package database or npm and Composer lockfiles. That package list then gets matched against one or more CVE databases that link known vulnerabilities to affected version ranges.
The result is a list of found vulnerabilities along with severity, affected package, installed version, and, where available, the version in which the gap was already fixed. That structured output can be evaluated programmatically, which is exactly what turns scanning into an automatable CI task rather than manual research.
3. Trivy versus Grype
Trivy from Aqua Security covers, beyond OS packages and application dependencies, misconfigurations, secrets embedded in the image, and license issues, making it a versatile tool for a single CI gate. Grype from Anchore focuses more narrowly on pure vulnerability detection but is considered especially precise in version evaluation, and pairs well with Anchore's SBOM tool Syft.
In practice the two tools differ less in fundamental capability than in details like scan speed, database freshness, and output format quality. Many teams deliberately run both in parallel, since different databases occasionally return different results, and a second source compensates for blind spots in a single database.
# GitHub Actions: Trivy scan as a mandatory gate before pushing to the registry
name: container-scan
on:
push:
branches: [main]
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t shop-app:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@0.24.0
with:
image-ref: "shop-app:${{ github.sha }}"
format: "table"
exit-code: "1"
severity: "CRITICAL,HIGH"
ignore-unfixed: true
- name: Push image to registry
if: success()
run: docker push registry.internal/shop-app:${{ github.sha }}
4. A CI gate that blocks the build on critical CVEs
An effective CI gate aborts the pipeline run with a non-zero exit code as soon as the scanner finds vulnerabilities above a defined severity threshold. In the example above, that happens through exit-code: 1 combined with severity: CRITICAL,HIGH, which means the following push step never runs and a vulnerable image never makes it into the registry at all.
It matters that the gate sits early in the workflow, right after the image is built and before any deployment step. A scan that runs but whose result is only shown as informational, without being enforced, quickly turns into an ignored warning that nobody takes seriously anymore once it becomes routine.
5. Choosing sensible severity thresholds
A gate that blocks on any vulnerability, no matter how low its severity, quickly leads to frustration in practice, since hardly any realistic base image is completely free of low-severity CVEs. A tiered approach works better, where critical and high vulnerabilities reliably stop the build while medium and low findings show up as warnings without automatically blocking anything.
The ignore-unfixed option is also worth using, since it hides vulnerabilities for which the package maintainer has not yet published a patch. Blocking a build over exactly that would be unproductive, because the team cannot do anything about it beyond waiting for an upstream fix or manually replacing the affected package.
6. Handling vulnerabilities in base images you do not control
A common problem is vulnerabilities buried deep in an official image's base distribution, in a library the operating system itself ships that nobody on your own team can directly influence. Here it helps to regularly move to leaner base images, such as distroless or Alpine-based variants, which inherently ship far fewer installed packages and therefore a smaller attack surface.
For cases where a fix simply does not exist yet but the risk is acceptable, there should be a documented exception process: a time-boxed ignore rule with a stated reason, an owner, and a follow-up date, rather than a silent, permanent suppression of the warning inside the scanner.
7. SBOM as a complement to a pure scan
A Software Bill of Materials, or SBOM, lists every component contained in an image along with its version, regardless of whether a vulnerability is currently known for it. Tools like Syft generate such a list in the standardized CycloneDX or SPDX format directly from a built image.
An SBOM's value shows most clearly when a new vulnerability in a widely used library goes public, as happened with log4shell. Instead of having to re-scan every single image from scratch, existing SBOMs let you instantly query which production images even contain the affected component, drastically cutting response time for such incidents.
8. Managing false positives and exceptions cleanly
Scanners occasionally flag vulnerabilities that are not actually exploitable in the concrete usage, for instance because the affected function of a library is never called in your own code. Ignoring such false positives blanket-style without documenting them eventually causes real findings to get lost in the noise.
Both Trivy and Grype support ignore files where individual CVE IDs can be excluded with a stated reason. Those files belong in version control and should be part of code review, so that every exception stays traceable and gets periodically checked for whether it is still justified.
9. Scanning in the registry versus in the CI pipeline run
Scanning directly in the CI pipeline catches vulnerabilities before an image even exists, proactively preventing vulnerable images from being created in the first place. Additional, periodic scanning of already-pushed images directly in the container registry instead covers vulnerabilities that only became publicly known after the original build, while the image itself sits unchanged in the registry.
Both layers complement each other well: the pipeline gate stops new gaps from being created, while registry scanning continuously re-evaluates images already in circulation, surfacing risks that became known later, long before an affected image gets deployed again.
| Tool | License | Scan method | Distinguishing trait |
|---|---|---|---|
| Trivy | Apache 2.0 | OS packages, dependencies, secrets, IaC | all-in-one tool with very broad coverage |
| Grype | Apache 2.0 | OS packages and application dependencies | precise version evaluation, pairs with Syft |
| Docker Scout | commercial with free tier | registry-integrated scanning | built directly into the Docker ecosystem |
| Snyk Container | commercial with free tier | dependencies with fix recommendations | strong prioritization and remediation guidance |
Mironsoft
Security audits, OWASP-compliant hardening, and secure architecture
Applications that actually hold up against a real attack attempt?
We review existing applications for classic OWASP vulnerabilities, insecure authentication, and missing input validation, then build an architecture that structurally reduces attack surface instead of just patching individual symptoms.
Security Audit
Systematically checking OWASP Top 10, auth flows, and input validation for vulnerabilities.
Secure Architecture
Building rate limiting, encryption, and access controls correctly from the ground up.
Incident Readiness
Establishing logging, monitoring, and response processes for when things go wrong.
10. Summary
Container Image Scanning in CI/CD: The Essentials at a Glance
Core problem
Base images carry code you did not write, where new vulnerabilities can surface any time.
Tools
Trivy covers broadly, Grype is especially precise at pure vulnerability detection.
Enforcement
A CI gate checking the exit code reliably blocks the build on critical findings.
Handling residual risk
Time-boxed, documented exceptions instead of silent, permanent suppression.