How to Scan Docker Images for Vulnerabilities with Trivy and Docker Scout

Published:

Think your Docker images are safe because you used an official base image?
They still can hide critical CVEs in OS packages or app dependencies, and you’ll only find them if you scan.
This guide shows how to run quick, practical scans with Trivy and Docker Scout, how to read the CVE output, and which findings to act on first.
You’ll get the exact commands for local checks, CI gating, and private registries so you can stop shipping vulnerable images and fix them fast.

Immediate Steps to Scan Docker Images Using Practical Commands

GDeR5-ubV2Ko5ZrasmGKBw

Scanners look at two things when they dig into Docker images: OS packages (apk, apt/dpkg, rpm) sitting in the base layer and application dependencies (npm, PyPI, Gems, Maven, Go modules) pulled from lockfiles or baked into directories. To check for vulnerabilities, you install the scanner, pull or build an image locally, run a single scan command, look at the CVE output table showing package names and severity levels, then figure out which packages need updates.

Here’s how a basic Trivy scan works:

  1. Install Trivy by grabbing the release binary and dropping it into a directory on your $PATH. Check it with trivy version.
  2. Pull the target image using docker pull myapp:latest if you’re scanning something remote, or just build it locally.
  3. Run trivy image myapp:latest to scan. Trivy spits out a table with columns for vulnerability ID, package name, installed version, fixed version, and severity.
  4. Look at findings marked CRITICAL or HIGH. The output includes rows like “libssl3, CVE-2024-5535, CRITICAL, Installed: 3.0.11-1~deb12u2, Fixed: 3.0.13-1.”
  5. Figure out fixes by checking the “Fixed” column. If a version’s listed, update your Dockerfile base image or package manager commands, rebuild, and rescan.

Docker Scout’s built into Docker Engine 24+ and doesn’t need separate installation if you’re already using Docker Desktop or the Docker CLI. You can scan right away with docker scout cves myapp:latest and see fix recommendations in the terminal or Docker Desktop Security tab. Grype’s similar in the install, scan, review flow, but adding --fail-on critical,high in CI pipelines makes sure the build fails only when you can actually patch something.

Scanning local images before pushing to registries (Docker Hub, Amazon ECR, Google GCR, Harbor) catches vulnerabilities early and keeps insecure packages out of production. Each scanner compares package versions against CVE databases like Red Hat Security Data and Ubuntu Security Notices, giving you a prioritized report you can act on within minutes of finishing a build.

Understanding Docker Image Vulnerability Checks

93bY0mzCWXWJz5bF38pHqQ

When a scanner analyzes a Docker image, it unpacks each layer and pulls out the list of OS packages installed by package managers (apt, yum, apk) and application dependencies defined in lockfiles (package-lock.json, requirements.txt, Gemfile.lock, pom.xml, go.mod). Then it cross-references these packages and their versions against public vulnerability databases (CVE feeds, vendor security notices, and language-specific advisory sources) to spot known security issues. This layer-aware inspection covers both the base image you’re inheriting from (like node:20-alpine) and every RUN or COPY instruction that adds libraries or binaries.

Scanners detect four broad categories of issues:

OS packages: outdated libraries like libssl, glibc, or curl that ship with the base image distribution.
Language dependencies: vulnerable versions of npm modules, Python wheels, or Java JARs bundled into your application layer.
Secrets and misconfigurations: embedded API keys, SSH private keys, or insecure Dockerfile directives (exposed ports, overly permissive file permissions).
Outdated base images: scanning tools often recommend switching from an old Debian or Alpine version to a newer, patched release.

No scanner guarantees 100% coverage. Databases rely on vendors to publish CVE records, so zero-day vulnerabilities and proprietary package issues can slip through until someone discloses them. Scanners also differ in their matching heuristics, which means the same image might show slightly different findings across Trivy, Grype, and Docker Scout. Running multiple scanners and reconciling their outputs cuts down blind spots for mission-critical images.

Using Trivy for Fast Docker Image Scanning

3psZFf_0V2uw4imr3pJL6w

Trivy’s an open-source, single-binary vulnerability scanner with more than 24,000 GitHub stars, covering OS packages, language dependencies, IaC misconfigurations, and embedded secrets in one tool. It supports multiple output formats (table, JSON, SARIF) and runs in local, client-server, or CI/CD modes, which makes it flexible enough for developer laptops and production pipelines.

Installing Trivy

Download the latest release archive from the Trivy GitHub releases page, extract the binary, and move it into a directory on your system $PATH (like /usr/local/bin on macOS or Debian/Ubuntu). Validate the install by running trivy version. You should see output listing the version number and database schema. If the command fails, confirm the binary has execute permissions (chmod +x trivy) and that the directory’s included in your shell’s $PATH variable.

Running a Basic Image Scan

After pulling or building an image, run trivy image myapp:latest to scan. Trivy downloads the latest vulnerability database on first run (cached locally for later scans) and prints a table with these columns: Library (package name), Vulnerability ID (CVE number), Severity (CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN), Installed Version, and Fixed Version. Each row matches one CVE in one package. If a package has multiple vulnerabilities, each appears on its own line.

The default table output’s human-readable, but you can switch to JSON for automation by tacking on -f json or produce SARIF for upload to GitHub Security with -f sarif. Trivy exits with a non-zero status code if it finds vulnerabilities, which is handy for failing CI builds.

Filtering and Output Formats

You can filter results to show only specific severities by adding --severity CRITICAL,HIGH to cut noise and focus on stuff you can actually fix. The --ignore-unfixed flag hides vulnerabilities that have no patch available yet, reducing false-positive alerts when a vendor’s acknowledged a CVE but hasn’t released a fix. To generate a structured report for integration with ticketing or SIEM systems, use -f json -o results.json or -f template --template "@/path/to/html.tpl" to render custom HTML reports.

Scanning Private Registries

To scan images in a private registry, first authenticate Docker (docker login registry.example.com) so Trivy can pull the image manifest and layers. Then run trivy image registry.example.com/myapp:v1.2.3. Trivy respects Docker’s credential store and uses the same token you used for docker pull. If you need to scan without pulling the image locally, use the --input flag to feed Trivy a saved tarball (docker save myapp:latest > image.tar then trivy image --input image.tar).

Recommended Trivy flags for CI and operational workflows:

  1. --ignore-unfixed to filter out vulnerabilities with no vendor fix, cutting noise in CI logs.
  2. --severity CRITICAL,HIGH to focus only on high-risk findings and avoid blocking builds on informational stuff.
  3. -f sarif -o trivy-results.sarif to produce SARIF output for GitHub Advanced Security or other SARIF-compatible dashboards.
  4. --exit-code 1 to force Trivy to return non-zero if vulnerabilities are found, so CI jobs fail appropriately.
  5. --cache-dir /custom/cache to control where Trivy stores its database, useful in ephemeral CI runners to keep the DB across runs.
  6. --no-progress to disable progress bars in non-interactive CI environments, keeping logs clean and parseable.

Grype and SBOM-Based Analysis for Docker Images

S3a_gokHXFCnjOx3Virl7Q

Grype’s an open-source vulnerability scanner that pairs tightly with Syft, a software bill of materials (SBOM) generator. This combo’s a strong fit when compliance frameworks require auditable SBOMs or when you want to decouple vulnerability scanning from the image build process. Grype supports industry-standard SBOM formats (CycloneDX, SPDX) and can rescan a stored SBOM against updated vulnerability feeds without rebuilding the entire image.

The SBOM-first workflow starts by generating an SBOM with Syft (syft myapp:latest -o cyclonedx-json > sbom.json), storing that artifact alongside your image in a registry or artifact repository, then scanning it with Grype (grype sbom:sbom.json). This approach’s particularly valuable when managing hundreds of images in a registry: you generate SBOMs once per build, store them as metadata, and periodically rescan them against fresh vulnerability data without re-pulling gigabytes of image layers.

SBOM-First Workflow

To implement SBOM-based scanning, first install Syft and Grype (both are single-binary releases from the Anchore GitHub organization). Run syft myapp:latest -o cyclonedx-json > myapp-sbom.json to capture a CycloneDX SBOM listing every package, library, and version in the image. Upload this SBOM to your artifact store (Artifactory, S3, or as a GitHub release asset). When you need to scan, run grype sbom:myapp-sbom.json to analyze the SBOM file directly. Grype checks its local vulnerability database and prints CVE findings just like a live image scan, but faster and without network I/O to the registry.

Key workflow steps:

  1. Generating the SBOM: Use syft myapp:latest -o cyclonedx-json to produce a JSON SBOM containing all OS packages and language dependencies. Store this file as a build artifact.
  2. Scanning the SBOM: Run grype sbom:myapp-sbom.json to do vulnerability detection against the SBOM. It’s faster than re-scanning the full image and can be scheduled daily to catch newly disclosed CVEs.
  3. Failing on severity: Add --fail-on critical,high to make Grype exit non-zero only when critical or high-severity vulnerabilities are found, avoiding CI blocks on low-priority stuff.
  4. Using –only-fixed: Include --only-fixed to filter out CVEs that have no vendor patch available, reducing noise from “will not fix” advisories and keeping CI feedback actionable.

Docker Scout Integration for Developer-Friendly Image Scanning

I-koYLClXWWrieyEAclz9g

Docker Scout’s built into Docker Desktop and the Docker CLI starting with Docker Engine 24, offering a developer-friendly UI and tight integration with Docker Hub. Scout scans images for vulnerabilities and gives contextual fix recommendations (suggesting a newer base image tag or a specific package update) without leaving the Docker Desktop interface or command line.

The free tier of Docker Scout covers unlimited public images and a limited number of private repositories (the exact limit depends on your Docker subscription; personal free accounts typically get three private repos). Broader coverage for private repositories requires a paid Docker subscription (Team or Business tier). Scout maps vulnerabilities to Docker Hub’s upstream metadata, so if you’re using official images (node, nginx, python), it can recommend switching from node:18 to node:20-alpine or updating an Alpine package to a patched version.

Docker Scout’s key strengths include:

UI view in Docker Desktop: Open Docker Desktop, head to the Images tab, select an image, and click the “View in Scout” button to see a visual breakdown of CVEs, affected layers, and recommended actions.
Fix suggestions: Scout highlights which base image tag or package version will resolve a CVE, making remediation faster than cross-referencing upstream changelogs manually.
Registry integration: Scout indexes images pushed to Docker Hub automatically, providing continuous monitoring and alerting when new CVEs are published that affect your images, even after they’ve been deployed.

To scan an image with Scout from the CLI, run docker scout cves myapp:latest. The output shows a list of CVEs, affected packages, severity, and (when available) a recommended version or base image tag that fixes the issue. If you prefer JSON output for scripting, add --format json. Scout also supports comparing two images (docker scout compare old-image new-image) to see which vulnerabilities were introduced or fixed between versions, which is useful during base image upgrades or dependency updates.

Adding Image Scanning to CI/CD Pipelines

617_HFUtVwubxtF_HWY42w

Integrating vulnerability scanning into CI/CD pipelines makes sure every image gets checked before it reaches production, shifting security left and catching issues at build time. The recommended process is: build the image, scan for vulnerabilities, upload results to a security dashboard (SARIF, JSON, or vendor-specific format), fail the pipeline if critical issues are found.

GitHub Actions Workflow

GitHub Actions supports SARIF upload to the Security tab, making it easy to centralize vulnerability findings alongside code-scanning alerts. A typical workflow step builds the Docker image, runs Trivy or Grype to produce a SARIF file, and uploads that file using the github/codeql-action/upload-sarif action. To make sure results are visible even when the build fails, add if: always() to the upload step. This guarantees the SARIF gets uploaded whether the scan passes or fails, so you can review findings in the Security tab without rerunning the job.

Example snippet (conceptual):

- name: Build image
  run: docker build -t myapp:${{ github.sha }} .
- name: Scan with Trivy
  run: trivy image --exit-code 0 --format sarif --output trivy-results.sarif myapp:${{ github.sha }}
- name: Upload SARIF
  if: always()
  uses: github/codeql-action/upload-sarif@v2
  with:
    sarif_file: trivy-results.sarif

The --exit-code 0 flag prevents Trivy from failing the build early, letting the upload step run. You can add a separate step afterward that fails on severity thresholds.

GitLab and Jenkins Patterns

GitLab CI and Jenkins pipelines follow a similar structure. In GitLab, define a job that runs trivy image --format json myapp:latest > gl-container-scanning-report.json and declare the report as a container_scanning artifact type. GitLab’s Security Dashboard will parse and display findings. In Jenkins, use a shell step to run Grype or Trivy, capture the JSON output, and publish it as a build artifact or send it to a SIEM via webhook. The Anchore scan-action equivalent in Jenkins is often a Jenkinsfile stage that wraps the Grype CLI and checks the exit code to mark the build unstable or failed.

Multi-Stage Build and Digest Pinning

Multi-stage Dockerfiles compile code in an intermediate builder stage and copy only the final artifacts into a slim production image. Scanning the builder stage produces noise from compilers, build tools, and development dependencies that never reach production. To avoid this, scan only the final stage: tag the production image separately (docker build --target production -t myapp:latest .) and run your scanner against that tag. This keeps CI feedback focused on vulnerabilities that’ll actually run in production.

Pinning base images to digests makes sure you get reproducibility and accurate scanning. Instead of FROM node:20-alpine, use FROM node:20-alpine@sha256:abcd1234.... This locks the base image to a specific layer hash, so scans always map to the same packages and you can track which digest introduced a CVE. Update the digest periodically by pulling the latest tag, noting its digest (docker inspect --format='{{.RepoDigests}}' node:20-alpine), and committing the new hash to your Dockerfile.

CI System Scanner Key Flags/Notes
GitHub Actions Trivy –format sarif, upload with if: always()
GitLab CI Grype –output json, declare as container_scanning artifact
Jenkins Trivy or Grype Shell step, parse exit code, publish JSON to SIEM

Interpreting Scanner Output and Prioritizing Fixes

P1CZGnJeV-OPNiThxruQ3A

Scanner output usually shows findings in a table or JSON structure with these fields: CVE identifier, affected package name, installed version, fixed version (if available), severity (CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN), and sometimes a CVSS score or exploit maturity indicator. Reading this output effectively means mapping each CVE to the package it affects, checking whether a fix is available, and prioritizing based on both severity and deployment impact.

Start by filtering results to CRITICAL and HIGH severities, since these represent exploitable vulnerabilities or data exposure risks. If a CVE lists a fixed version, you can remediate by updating the package or base image. If the fixed column’s empty or marked “will not fix,” the issue’s either unpatched by the vendor or requires an architectural change (like switching to a different library). Focus on CVEs with available fixes first, since they can be resolved with a Dockerfile change and rebuild.

Triage also depends on deployment footprint. A critical CVE in an image running across 100 production pods is higher priority than the same CVE in a rarely used internal tool. Map scanner findings to runtime deployments by correlating image digests with Kubernetes pod specs or container registry usage logs, and fix high-impact images before low-traffic ones.

Practical prioritization steps:

  1. Filter to CRITICAL/HIGH severities to cut noise and focus on actionable issues.
  2. Check the “Fixed Version” column. Prioritize CVEs with patches over those marked “no fix available.”
  3. Cross-reference CVE IDs with runtime usage. Scan Kubernetes clusters to see which images are deployed and how many replicas run each digest.
  4. Use --ignore-unfixed (Trivy) or --only-fixed (Grype) in CI to avoid blocking builds on unpatched vendor issues, reducing false-positive fatigue and keeping CI feedback relevant.

Remediation and Rebuilding Strategies for Vulnerable Docker Images

wDYL0C3wXt2khClkzWLYSg

Patching container images means updating the Dockerfile to pull newer base images or install updated packages, then rebuilding and rescanning to confirm the fixes. The most common remediation’s refreshing the base image: if your Dockerfile starts with FROM debian:bullseye and the scanner reports outdated OpenSSL, switching to FROM debian:bookworm or the latest bullseye point release often resolves dozens of CVEs in one change.

For OS-level packages, add explicit version pins or apt-get upgrade commands in your Dockerfile to pull patched libraries. For application dependencies, update your package-lock.json, requirements.txt, or go.mod to newer versions that address the CVE, then rebuild. After each change, rescan the new image to verify the vulnerability’s gone and make sure the update didn’t introduce new issues.

Remediation workflow:

  1. Refresh the base image: Pull the latest tag for your base image (docker pull node:20-alpine) and note the new digest. Update your Dockerfile to pin to that digest (like FROM node:20-alpine@sha256:new-hash).
  2. Update OS packages: Add a RUN apt-get update && apt-get upgrade -y layer in your Dockerfile to install the latest security patches for system libraries.
  3. Fix application dependencies: Bump vulnerable npm, pip, or Maven package versions in your lockfiles to patched releases. Run npm audit fix, pip-audit, or equivalent tools to automate updates when possible.
  4. Rebuild the image: Run docker build -t myapp:latest . to generate a new image incorporating all updates.
  5. Rescan and verify: Run trivy image myapp:latest or grype myapp:latest to confirm the CVE’s resolved. Check that no new vulnerabilities were introduced by the updated dependencies.

When scanning with the --file <path/to/Dockerfile> option (supported by some tools), the scanner maps each vulnerability to the Dockerfile instruction that introduced it, making it easier to see whether the issue came from the base image, a RUN command, or a COPY of application code. Prefer smaller, more frequently updated base images (Alpine, Distroless) over large, infrequently patched distributions to minimize the number of packages and reduce your vulnerability surface.

Comparing Open-Source and Commercial Image Scanners

WgIDzkv_X8CsV6aWzF1i_A

Open-source scanners (Trivy, Grype) and commercial tools (Docker Scout paid tiers, Snyk Container, Aqua Security) each have distinct strengths and limitations. Trivy offers the broadest coverage in a single open-source tool, scanning OS packages, language dependencies, IaC misconfigurations, and embedded secrets without needing a vendor account. Grype excels in SBOM-driven workflows and compliance scenarios where auditors need a portable bill of materials and reproducible scans. Docker Scout provides the best developer experience with fix recommendations and Docker Hub integration, but larger private repository coverage requires a paid subscription.

Commercial scanners typically add features like policy enforcement, runtime correlation (linking image CVEs to running containers), and vendor support, but at the cost of subscription fees and vendor lock-in. For small teams and open-source projects, Trivy and Grype provide production-grade scanning without licensing costs. For enterprises with hundreds of private images and regulatory requirements, a commercial tool’s compliance reporting and support SLA might justify the expense.

Tool Strength Limitations
Trivy Broad coverage (OS, deps, misconfigs, secrets); open-source; single binary No built-in policy engine; requires scripting for complex workflows
Grype Strong SBOM support; pairs with Syft; compliance-friendly Fewer built-in integrations; less developer UX polish than Scout
Docker Scout Best UX; Docker Hub integration; actionable fix recommendations Free tier limited to few private repos; paid tiers required for scale

Final Words

Start scanning images locally with Trivy, Grype, or Docker Scout. The article gave the exact commands, a 5-step quick workflow, and a sample CVE row so you see what real output looks like.

You learned what scanners inspect (OS packages, language deps, image layers), how to read CRITICAL/HIGH results, and how to wire scans into CI/CD and SBOM workflows for consistent checks.

If you follow those steps you’ll know exactly how to scan docker images for vulnerabilities, prioritize fixes, and rebuild safely. Small checks, less night work.

FAQ

Q: How to check Docker image for vulnerabilities and ensure it’s safe before deployment?

A: To check a Docker image for vulnerabilities and ensure it’s safe before deployment, run a layer-aware scanner locally (e.g., Trivy/Grype), review CRITICAL/HIGH CVEs, and fix or update the base image before pushing.

Q: Which security tool can be used to scan Docker images for vulnerabilities?

A: The security tools you can use to scan Docker images include Trivy, Grype (with Syft for SBOMs), and Docker Scout (requires Docker Engine 24+); choose based on CI needs, SBOM support, and UX.

curtisharmon
Curtis has spent over two decades guiding hunters and anglers through the backcountry of Montana and Wyoming. His expertise in elk hunting and fly fishing has made him a sought-after voice in the outdoor community. Curtis combines traditional woodsmanship with modern techniques to help readers succeed in the field.

Related articles

Recent articles