Grype is a vulnerability scanner from Anchore that finds known security issues in container images, filesystems, and Software Bills of Material (SBOMs). It catalogs every package in a target — operating-system packages (apk, dpkg, rpm, portage, pacman) and language ecosystem packages (Java, JavaScript, Python, Ruby, Go, .NET, Rust, Erlang/Elixir Hex, Bitnami) — matches each package against a curated vulnerability database, and prints the CVEs (or GHSAs, RHSAs, ELSAs, ALAS advisories, MSRC entries, etc.) that apply.
This guide covers everything an operator needs to install grype, configure it, run scans against any supported source, manage the vulnerability database, and integrate scans into CI/CD. For architecture, internals, and code-level details, see the companion Grype Administrator & Developer Guide.
1. Quick Start
The fastest path from "no grype" to "first scan" is the official install
script: it pulls the latest release for your OS and architecture from
GitHub, verifies the SHA-256 checksum, and drops the grype binary into
./bin/grype.
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin
Then scan a public image:
grype alpine:3.20
✔ Vulnerability DB [no update available]
✔ Pulled image
✔ Loaded image alpine:3.20
✔ Parsed image sha256:b0c9d60fc5e3e1dabe98d8a0a68cfe70c5d2a2a0b3a
✔ Cataloged contents 14e3a3...
├── ✔ Packages [15 packages]
└── ✔ Executables [17 executables]
✔ Scanned for vulnerabilities [0 vulnerability matches]
├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible
└── by status: 0 fixed, 0 not-fixed, 0 ignored
No vulnerabilities found
That's the entire workflow: point at a target, get a verdict.
Tip
If you don't want to install at all, run grype straight from the published Docker image — the database is downloaded into the container's cache on first run:
docker run --rm anchore/grype:latest alpine:3.20
What you'll need
- [x] A 64-bit Linux, macOS, or Windows host (x86_64, arm64, ppc64le, or s390x)
- [x] Outbound HTTPS (
grype.anchore.iofor the database,github.comfor releases) - [x] Optional but recommended: Docker (or Podman) for image-source support
- [x] ~200 MB of free disk space for the vulnerability database cache
Headline features
- Scan anything: Docker images, OCI archives, OCI image directories, plain filesystems, single files, Singularity SIF containers, registry references (no daemon required), Syft JSON SBOMs, CycloneDX SBOMs, SPDX SBOMs, package URLs (PURLs), and CPEs.
- Many ecosystems: apk, dpkg, rpm, portage, pacman, Bitnami, Java (Maven), JavaScript (npm), Python (PyPI), Ruby (RubyGems), Go modules, .NET, Rust crates, Erlang/Elixir Hex, plus Microsoft (MSRC) for KB-style updates.
- Multiple output formats:
table(default),json,cyclonedx-json,cyclonedx-xml,sarif, and Go-template formatting. - CI-ready exit codes:
--fail-on criticalreturns exit code2so pipelines can break the build when a severity threshold is exceeded. - VEX-aware: ingest OpenVEX or CSAF VEX documents to suppress false-positive matches that you've already triaged.
- Offline-friendly: the database is a single tar archive that can be mirrored, imported with
grype db import, and used without internet.
What to try next
- See the Installation section for package managers, distro repositories, and Docker variants.
- See Common Tasks for ready-to-paste recipes including SARIF output for GitHub code scanning, SBOM-first pipelines, and air-gapped operation.
- See the Admin Guide for the full configuration reference and security model.
2. Installation
Grype is a single Go binary with no runtime dependencies — installation is just "get the binary onto $PATH". Pick the method that matches how you manage other CLI tools.
2.1 System requirements
| Requirement | Detail |
|---|---|
| Operating system | Linux (kernel 3.10+), macOS 11+, or Windows 10+ |
| Architecture | amd64, arm64, ppc64le (Linux only), s390x (Linux only) |
| Disk space | ~50 MB for the binary, ~200 MB for the active vulnerability DB |
| Memory | 256 MB minimum; 1–2 GB recommended for large images or multi-arch SBOMs |
| Network | HTTPS to grype.anchore.io and github.com (only for installs and DB updates — scans themselves are local) |
| Optional | Docker or Podman daemon if you want to scan local images by tag |
Note
Grype releases ship as
tar.gzarchives for Linux/macOS,ziparchives for Windows, plus signed.deband.rpmpackages. Container images are published to both Docker Hub (anchore/grype) and GHCR (ghcr.io/anchore/grype) inlatest,nonroot, anddebugflavors, coveringamd64,arm64v8,ppc64le, ands390x.
2.2 Install methods
Install script (recommended for one-off installs)
The official install.sh is the path used in the project's own
documentation and CI. It infers your OS and architecture, downloads the
right archive, verifies the SHA-256 checksum, optionally verifies the
release signature with cosign, and installs the binary to a directory you
choose.
# default: installs to ./bin/grype
curl -sSfL https://get.anchore.io/grype | sh -s --
# install to /usr/local/bin (system-wide)
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin
# pin a specific tag (do this in CI for reproducible builds)
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin v0.96.1
# enable cosign signature verification (requires `cosign` on PATH)
curl -sSfL https://get.anchore.io/grype | sh -s -- -v -b /usr/local/bin
The script accepts these flags: -b DIR (install dir, default ./bin),
-d / -dd (debug / trace logging to stderr), -v (cosign signature
verification, requires release ≥ v0.72.0 and the cosign binary on
PATH), and a positional TAG argument to pin a release.
Important
Always install grype to a location that your CI runner has on
$PATHand permission to execute. The default./binis a current-directory install — convenient locally, surprising in pipelines. Use-b /usr/local/bin(or~/.local/binfor non-root installs) on shared runners.
Homebrew (macOS, Linux)
The release ships a Homebrew cask (not a formula) so the prebuilt upstream binary is used directly:
brew install anchore/grype/grype
Upgrades flow through brew upgrade grype. If you previously installed the
old grype formula, the cask conflicts with it and Homebrew will tell you
to uninstall the formula first.
Debian / Ubuntu (.deb)
The release publishes signed .deb packages alongside the tarballs:
curl -OL https://github.com/anchore/grype/releases/latest/download/grype_<version>_linux_amd64.deb
sudo dpkg -i grype_<version>_linux_amd64.deb
Replace <version> with the exact release version (e.g. 0.96.1) — the
filenames embed the version number.
Red Hat / Fedora / Rocky / Alma (.rpm)
curl -OL https://github.com/anchore/grype/releases/latest/download/grype_<version>_linux_amd64.rpm
sudo rpm -i grype_<version>_linux_amd64.rpm
Manual binary install (any Linux/macOS/Windows)
Download the archive matching your OS/ARCH from the releases page,
extract it, and place grype (or grype.exe on Windows) on $PATH:
curl -OL https://github.com/anchore/grype/releases/latest/download/grype_<version>_linux_amd64.tar.gz
tar -xzf grype_<version>_linux_amd64.tar.gz
sudo install -m 0755 grype /usr/local/bin/grype
Docker / OCI image
The published image is a self-contained binary plus the bare minimum to run
it. The first scan downloads the vulnerability DB into the container's
/.cache directory, so persist that directory across runs to avoid
re-downloading on every invocation.
docker pull anchore/grype:latest
# scan a remote image (no Docker socket needed — uses registry mode internally)
docker run --rm anchore/grype:latest alpine:3.20
# scan a local image (mount the docker socket)
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
anchore/grype:latest myorg/myimage:tag
# scan a local directory (mount the directory read-only)
docker run --rm -v "$PWD":/src:ro anchore/grype:latest dir:/src
# persist the DB cache across runs
docker run --rm \
-v "$HOME/.cache/grype":/.cache/grype \
anchore/grype:latest alpine:3.20
Image variants:
| Tag | Purpose |
|---|---|
anchore/grype:latest |
Default — root user, full toolchain. |
anchore/grype:nonroot |
Same binary, runs as a non-root UID for hardened CI runners. |
anchore/grype:debug |
Shipped alongside latest for troubleshooting. |
anchore/grype:<version> |
Pinned release. Always pin in production pipelines. |
anchore/grype:<version>-nonroot |
Pinned non-root variant. |
Build from source
You'll need Go 1.26.2 or newer (matching go.mod):
git clone https://github.com/anchore/grype
cd grype
go install ./cmd/grype
Or use the project's task runner (which wires up the same release pipeline the CI uses):
# install all required tools into ./.tool
task tools
# build a snapshot binary
task snapshot
Warning
A development build sets
versionto[not provided]. Thegrype versioncommand will report this, the application-update check is skipped, and the install-script signature-verification flag is meaningless for that binary. Usetask release(and the production CI pipeline) to produce a real release.
2.3 Post-install verification
Confirm the binary, then download the vulnerability database for your schema:
grype version
Application: grype
Version: 0.96.1
BuildDate: 2026-04-21T18:42:11Z
GitCommit: 6d0b66c8...
GitDescription: v0.96.1
Platform: linux/amd64
GoVersion: go1.26.2
Compiler: gc
Syft Version: v1.42.5-0.20260412145408-e4203224943d
Supported DB Schema: 6
grype db update
✔ Vulnerability DB [updated]
Vulnerability database updated to latest version!
grype db status
Path: /home/you/.cache/grype/db/6/vulnerability.db
Schema: v6.0.0
Built: 2026-04-27T01:31:06Z
From: https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst?checksum=sha256%3A...
Status: valid
If Status: valid is reported, you're ready to scan. If it reports
invalid, see the Troubleshooting section.
2.4 Shell completion
Grype ships a built-in completion generator for bash, zsh, and fish, with a
custom completion that lists local Docker images by repo:tag — handy for
grype <TAB>.
# bash (per-user)
grype completion bash > /etc/bash_completion.d/grype # Linux, root
grype completion bash > /usr/local/etc/bash_completion.d/grype # macOS
# zsh
echo "autoload -U compinit; compinit" >> ~/.zshrc
grype completion zsh > "${fpath[1]}/_grype"
# fish
grype completion fish | source
grype completion fish > ~/.config/fish/completions/grype.fish
2.5 Uninstall
| Method | Command |
|---|---|
| Install script | sudo rm /usr/local/bin/grype (or whatever -b you used) |
| Homebrew | brew uninstall --cask grype |
.deb |
sudo apt remove grype |
.rpm |
sudo rpm -e grype |
| Docker | docker rmi anchore/grype:latest |
| Cache | rm -rf ~/.cache/grype && rm -rf ~/.config/grype |
3. Configuration
Grype is fully usable with zero configuration — defaults work for the common case. Configuration kicks in when you want to set fail thresholds, ignore lists, registry credentials, or matcher tuning persistently rather than passing flags every time.
3.1 Configuration sources, in precedence order
When grype starts, it merges values from these sources (later wins):
- Built-in defaults (compiled in).
- The first config file found via
-c PATH,--config PATH, or in the default search path (see below). - Environment variables prefixed
GRYPE_*(with_separator for nested keys — e.g.GRYPE_DB_AUTO_UPDATE=false,GRYPE_REGISTRY_AUTH_USERNAME). - Command-line flags.
3.2 Config-file search order
If you don't pass -c PATH, grype looks for the first file that exists, in
this order:
./.grype.yaml./.grype/config.yaml~/.grype.yaml~/.config/grype/config.yaml/etc/grype/config.yaml(system-wide)
YAML, JSON, and TOML are all accepted (file extension determines format), but YAML is canonical and what every example below uses.
3.3 A practical starter config
This config sets sane CI-friendly defaults: JSON to stdout, fail on high-or-worse severity, ignore unfixed kernel-headers noise, and a sample ignore rule.
# .grype.yaml — typical CI configuration
# pick output format(s); use "<format>=<file>" for fan-out
output:
- json
# break the build for High or Critical findings
fail-on-severity: high
# only show fixable vulnerabilities (drop unfixed/wont-fix)
only-fixed: true
# show suppressed/ignored matches in the table presenter
show-suppressed: false
# normalize the table by CVE rather than per-namespace ID
by-cve: true
# image platform for multi-arch repos
platform: linux/amd64
# database tuning
db:
auto-update: true
validate-age: true
max-allowed-built-age: 120h
update-url: https://grype.anchore.io/databases
# ignore rules (each rule must match all listed fields)
ignore:
# silence a known false positive in our base image
- vulnerability: CVE-2024-0001
package:
name: openssl
version: "3.0.*"
# ignore wont-fix matches for noise reduction
- fix-state: wont-fix
Drop that into ~/.grype.yaml (user-wide) or ./.grype.yaml
(repo-specific). Override individual values with flags or GRYPE_*
environment variables on a per-invocation basis.
Tip
grype config(no args) prints the fully merged config — defaults plus everything from your file plus current env. Pipe it throughlesswhen you're hunting down "why is this setting wrong":grype config | less
3.4 Environment variables you'll actually use
Every config key has a corresponding GRYPE_* env var. The most common:
| Env var | Equivalent flag/key | Purpose |
|---|---|---|
GRYPE_DB_CACHE_DIR |
db.cache-dir |
Override DB cache location (default ~/.cache/grype/db). |
GRYPE_DB_AUTO_UPDATE |
db.auto-update |
Disable on-demand DB updates (set to false in air-gapped CI). |
GRYPE_DB_UPDATE_URL |
db.update-url |
Point at a private mirror of the DB listing. |
GRYPE_DB_CA_CERT |
db.ca-cert |
Custom CA cert for TLS to a private DB mirror. |
GRYPE_REGISTRY_AUTH_AUTHORITY |
registry.auth[].authority |
Registry hostname. |
GRYPE_REGISTRY_AUTH_USERNAME |
registry.auth[].username |
Registry username (sensitive — redacted in logs). |
GRYPE_REGISTRY_AUTH_PASSWORD |
registry.auth[].password |
Registry password (sensitive — redacted in logs). |
GRYPE_REGISTRY_AUTH_TOKEN |
registry.auth[].token |
Registry bearer token (sensitive — redacted in logs). |
GRYPE_CHECK_FOR_APP_UPDATE |
check-for-app-update |
Disable the "new release available" notice. |
The full list lives in the Admin Guide → Configuration Reference.
3.5 The .grype.yaml ignore-rules format
An ignore rule is a list entry under ignore:. A match must satisfy
every specified field for the rule to apply (it's an AND). Empty fields
are wildcards. Available fields:
ignore:
- vulnerability: CVE-2024-1234 # CVE/GHSA/RHSA/etc. ID, regex allowed
reason: "patched out of band" # free-form, surfaces in JSON output
namespace: nvd:cpe # advisory namespace
fix-state: wont-fix # one of: fixed, not-fixed, wont-fix, unknown
vex-status: not_affected # one of: not_affected, fixed, affected, under_investigation
package:
name: openssl # package name (regex allowed)
version: "1.1.1*" # version glob
type: rpm # package type (apk, deb, rpm, npm, ...)
location: "/usr/lib/**" # file glob
upstream-name: openssl # source/upstream package name
match-type: exact-direct-match # one of: exact-direct-match, exact-indirect-match, cpe-match
Warning
An ignore rule with no fields silently ignores every match. Always include at least
vulnerability,package.name, orfix-state. Lint your config withgrype config | grep -A1 ignore:before committing.
3.6 Distro hint
If grype can't determine the distro of a directory or SBOM target (no
/etc/os-release, no PURL ?distro= qualifier), it'll warn and the OS
matchers won't run. Force it:
grype dir:/path/to/rootfs --distro debian:11
grype dir:/path/to/rootfs --distro rhel:9.4+eus # with a fix channel
grype dir:/path/to/rootfs --distro alpine@3.20 # @ separator
grype dir:/path/to/rootfs --distro ubuntu-focal # codename allowed
grype dir:/path/to/rootfs --distro opensuse-leap:15.2 # multi-segment names handled
The accepted separators are -, :, and @. opensuse-leap is
special-cased so the literal hyphen in the ID is preserved.
4. Core Usage
This section is the working reference: every input scheme grype accepts, every output format it produces, and every database subcommand.
4.1 The input model
A target reference can be given as a positional argument, piped on stdin, or — when scoped via a scheme prefix — read from a path or URL. The first positional argument is the user input string. If you pipe Syft JSON on stdin, grype reads from stdin and the positional arg is optional.
| Scheme prefix | What it scans | Example |
|---|---|---|
| (none) | Auto-detect: image tag, then dir, then file. | grype alpine:3.20 |
docker: |
Force Docker daemon lookup. | grype docker:alpine:3.20 |
podman: |
Force Podman daemon lookup. | grype podman:alpine:3.20 |
docker-archive: |
A docker save tarball on disk. |
grype docker-archive:./image.tar |
oci-archive: |
An OCI image tar (Podman/Skopeo). | grype oci-archive:./image.tar |
oci-dir: |
An OCI image layout directory. | grype oci-dir:./oci-layout |
singularity: |
A Singularity SIF container. | grype singularity:./image.sif |
dir: |
A filesystem directory. | grype dir:./project |
file: |
A single file on disk. | grype file:./binary |
sbom: |
A Syft, CycloneDX, or SPDX SBOM document. | grype sbom:./syft.json |
registry: |
Pull straight from a registry, no daemon. | grype registry:ghcr.io/anchore/grype:latest |
purl: |
A newline-separated file of PURLs. | grype purl:./purls.txt |
| (PURL literal) | A single Package URL on the command line. | grype 'pkg:apk/openssl@3.2.1?distro=alpine-3.20.3' |
cpes: |
A newline-separated file of CPEs. | grype cpes:./cpes.txt |
| (CPE literal) | A single CPE on the command line. | grype 'cpe:2.3:a:openssl:openssl:3.0.14:*:*:*:*:*' |
You can also pipe Syft output directly:
syft alpine:3.20 -o json | grype
Note
When the scheme is omitted, grype tries Docker first, then Podman, then directory/file detection. To skip the daemons entirely (e.g. on CI runners with no Docker socket), use
--from registry,oci-dir,oci-archiveor setGRYPE_DEFAULT_IMAGE_PULL_SOURCE=registry.
4.2 Top-level flags (root command)
These apply when grype is performing a scan (the root grype <target>
invocation). DB-subcommand flags are listed under DB management.
| Flag | Type | Default | Purpose |
|---|---|---|---|
-c, --config PATH |
string | (search path) | Use a specific config file. |
-o, --output FMT[=FILE] |
repeatable | table |
Output format. Repeat for fan-out (e.g. -o table -o json=out.json). |
--file PATH |
string | (stdout) | Default output file (used when -o doesn't specify one). |
--name NAME |
string | (detected) | Override the source name shown in reports. |
--distro DISTRO |
string | (detected) | Force-set the target distro (alpine:3.20, rhel:9.4+eus). |
--add-cpes-if-none |
bool | false |
Synthesize CPEs for packages without them (broadens NVD matches). |
--by-cve |
bool | false |
Normalize the report by CVE rather than per-namespace advisory ID. |
--exclude PATH |
repeatable | (none) | Glob of paths to skip when scanning a directory or image. |
--fail-on SEVERITY |
enum | (none) | Exit 2 if any non-ignored match meets/exceeds this severity. |
--only-fixed |
bool | false |
Suppress matches with no available fix. |
--only-notfixed |
bool | false |
Suppress matches that do have a fix. |
--ignore-states LIST |
csv | (empty) | Suppress matches with these fix-states (e.g. wont-fix,unknown). |
--platform PLATFORM |
string | (host) | OS/arch when pulling multi-arch images (e.g. linux/arm64). |
--show-suppressed |
bool | false |
Include ignored matches in the table presenter. |
--vex PATH |
repeatable | (none) | OpenVEX or CSAF VEX document to apply. |
-q, --quiet |
bool | false |
Suppress all logging output (still writes the report). |
-v / -vv |
counted | 0 |
Increase log verbosity (debug / trace). |
-h, --help |
flag | — | Show help. The root help also embeds the full default config. |
--version |
flag | — | Print version and exit. |
Severity levels for --fail-on, in increasing order: negligible, low,
medium, high, critical. (Anything with no severity classification is
treated as below negligible for the threshold check.)
4.3 Output formats
grype alpine:3.20 -o table # default, human-readable
grype alpine:3.20 -o json # full Grype JSON document
grype alpine:3.20 -o cyclonedx-json # CycloneDX SBOM with vulnerabilities
grype alpine:3.20 -o cyclonedx-xml # CycloneDX XML
grype alpine:3.20 -o sarif # GitHub-compatible SARIF 2.1.0
grype alpine:3.20 -o template -t ./report.tmpl # Go template
You can request multiple formats in one run, optionally writing each to a separate file:
grype myimage:latest \
-o table \
-o json=results.json \
-o sarif=results.sarif
The <format>=<file> syntax is parsed by parseOutputFlags in
internal/format/writer.go. If a path's parent directory doesn't exist,
grype will create it (mode 0755).
Available formats at a glance
| Format | Type | Use case |
|---|---|---|
table |
text | Human-readable terminal output. Default. |
json |
JSON | Pipe to jq, grype explain, or downstream tooling. |
cyclonedx-json |
JSON | CycloneDX BOM with vulnerabilities array. |
cyclonedx-xml |
XML | CycloneDX XML BOM. |
sarif |
JSON | GitHub Code Scanning, Azure DevOps, JetBrains Qodana. |
template |
text | Custom output via Go's text/template (Sprig functions enabled). |
Note
The
embedded-cyclonedx-vex-jsonandembedded-cyclonedx-vex-xmlformats from earlier releases have been folded intocyclonedx-jsonandcyclonedx-xmlrespectively. The old names still parse for backwards compatibility but emit a deprecation warning and will be removed at v1.0.
Table format
The default. One row per match, columns: NAME, INSTALLED, FIXED-IN,
TYPE, VULNERABILITY, SEVERITY. With --show-suppressed, ignored
matches are appended below.
JSON format
A single document with top-level keys: matches, ignoredMatches,
source, distro, descriptor (which contains name, version,
configuration, and db metadata). Stable schema — version is bumped
explicitly.
CycloneDX (JSON & XML)
Produces a CycloneDX BOM where each component is a package and the
vulnerabilities array lists matched advisories. The XML form includes
both an embedded section for backwards compatibility and the standard
CycloneDX vulnerability schema.
SARIF
A SARIF 2.1.0 document. Each result represents a vulnerability match
with the package as the location. Compatible with GitHub's
github/codeql-action/upload-sarif action and any tool that consumes
SARIF.
Template
The most flexible format — write a Go template against the same data model the JSON presenter uses, with all Sprig functions available:
grype alpine:3.20 -o template -t ./csv.tmpl > report.csv
A minimal CSV template:
{{- range .Matches -}}
{{.Vulnerability.ID}},{{.Vulnerability.Severity}},{{.Artifact.Name}},{{.Artifact.Version}},{{ index .Vulnerability.Fix.Versions 0 | default "" }}
{{ end -}}
Variables you'll reach for: .Matches[].Vulnerability.{ID,Severity,Description,DataSource},
.Matches[].Vulnerability.Fix.{Versions,State}, .Matches[].Artifact.{Name,Version,Type,Locations},
.Source.{Type,Target}, .Distro.{Name,Version,IDLike}, .Descriptor.Configuration,
.Descriptor.DB. See the Admin Guide for the full template variable
reference.
4.4 DB management
Every grype scan needs a vulnerability database — a sqlite file plus
metadata, packed into a tar.zst archive and published at
https://grype.anchore.io/databases/v6/latest.json. By default grype
auto-updates on first scan; you can also drive the cache manually.
grype db --help
Subcommands
| Command | Purpose |
|---|---|
grype db check |
Is a newer DB available? Exits 100 if yes (CI-friendly). |
grype db update |
Download and install the latest DB if newer. |
grype db status |
Print local DB path, schema, build date, validity. |
grype db list |
Print metadata for the latest published DB. |
grype db delete |
Remove the local DB cache (forces re-download). |
grype db import FILE|URL |
Install a DB from a local tarball or HTTPS URL. |
grype db diff [old] [new] |
Compare two DBs (URLs or paths) and report added/removed/modified packages. |
grype db search [args] |
Query the local DB for affected packages or vulnerabilities. |
grype db search vuln ID... |
Look up specific vulnerability IDs in the local DB. |
grype db providers |
List the data providers used to assemble the local DB. |
grype db check
grype db check
Installed DB version v6.0.0 was built on 2026-04-25T01:31:06Z
Updated DB version v6.0.0 was built on 2026-04-27T01:31:06Z
You can run 'grype db update' to update to the latest db
Add -o json for machine-parseable output. The exit code is 100 when an
update is available, 0 otherwise — useful in cron-style "warn me when
new" jobs.
grype db update
grype db update
Vulnerability database updated to latest version!
grype db status
grype db status -o json
{
"schemaVersion": "6.0.0",
"from": "https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst?checksum=sha256%3A...",
"built": "2026-04-27T01:31:06Z",
"path": "/home/you/.cache/grype/db/6/vulnerability.db",
"valid": true
}
grype db list
grype db list
Status: active
Schema: v6.0.0
Built: 2026-04-27T01:31:06Z
Listing: https://grype.anchore.io/databases/v6/latest.json
DB URL: https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst
Checksum: sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca4
-o raw gives a one-line JSON array for piping into other tooling.
grype db delete
grype db delete
Vulnerability database deleted
Removes the DB directory at ~/.cache/grype/db/6/. The next scan or
grype db update will re-download.
grype db import
# from a local archive (air-gapped install)
grype db import ./vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst
# from a URL with explicit checksum verification
grype db import 'https://mirror.example.com/grype/v6/vulnerability-db_v6.0.0.tar.zst?checksum=sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca4'
If the URL has a checksum=sha256:<hex> query parameter, grype verifies
the archive's digest against it before unpacking.
grype db diff
grype db diff /tmp/old/v6/vulnerability.db /tmp/new/v6/vulnerability.db
Outputs a table or JSON of which packages had vulnerabilities added,
removed, or modified between two DB snapshots. Use --packages, --vulns,
--epss, and --kev to narrow the diff to a single category.
grype db search
The full search command supports filtering by vulnerability, package name, PURL, CPE, ecosystem, and OS:
# packages affected by a specific CVE
grype db search --vuln CVE-2021-44228
# packages by name (any ecosystem)
grype db search --pkg log4j
# narrow to one CVE in one package
grype db search --pkg log4j --vuln CVE-2021-44228
# by PURL
grype db search --pkg 'pkg:rpm/redhat/openssl'
grype db search --ecosystem rpm --pkg openssl
# by CPE
grype db search --pkg 'cpe:2.3:a:jetty:jetty_http_server:*:*:*:*:*:*:*:*'
grype db search --pkg 'cpe:/a:jetty:jetty_http_server'
--ecosystem accepts apk, deb, rpm, npm, python, gem, golang,
maven, nuget, cargo, hex, etc. — any ecosystem grype catalogs.
grype db search vuln ID...
grype db search vuln CVE-2021-44228 GHSA-jfh8-c2jp-5v3q
ID PROVIDER PUBLISHED SEVERITY REFERENCE
CVE-2021-44228 nvd 2021-12-10 Critical https://nvd.nist.gov/...
GHSA-jfh8-c2jp-5v3q github 2021-12-10 Critical https://github.com/...
Aliases: vulnerability, vulnerabilities, vulns. Schema v6+ only.
grype db providers
grype db providers
NAME VERSION PROCESSOR DATE CAPTURED INPUT DIGEST
alpine 1.0.0 vunnel@3.2 2026-04-26 14:30:00 +0000 UTC xxh64:1234567...
github 1.0.0 vunnel@3.2 2026-04-26 14:31:00 +0000 UTC xxh64:abcdef0...
nvd 1.0.0 vunnel@3.2 2026-04-26 14:32:00 +0000 UTC xxh64:9876543...
4.5 The grype explain command
grype alpine:3.20 -o json | grype explain --id CVE-2024-1234
A prototype command that pretty-prints reasoning about a specific CVE match: which packages were affected, why, what versions fix it, and the chain of references. It currently requires Grype JSON on stdin; future versions may scan and explain in one call.
Warning
grype explainis marked as a prototype feature in the source — its output format and flags may change between releases. Don't build automation that parses its text output; use-o jsonupstream and parse that instead.
4.6 Exit codes
| Code | Meaning |
|---|---|
0 |
Scan completed successfully. |
1 |
A non-recoverable error occurred (network, parse, invalid args). |
2 |
Scan completed AND --fail-on threshold was met or exceeded. |
100 |
(grype db check only) An update is available. |
This is implemented in cmd/grype/cli/cli.go's WithMapExitCode. Use them
to make CI workflows precise: 2 is "build broken because of severity",
1 is "infrastructure broke".
5. Common Tasks
Each recipe follows the same shape: Goal, Prerequisites, Steps, Verify, If it fails.
Recipe 1: Scan a public Docker image
Goal: confirm an image you're about to run has no critical CVEs.
Prerequisites: grype installed; outbound HTTPS for the first DB pull.
Steps:
grype alpine:3.20 --fail-on critical
Verify: exit code 0 means no critical CVEs were matched (a 2
breaks your CI). The summary line 0 critical, X high, ... confirms the
scan ran.
If it fails:
- Network error? Set
GRYPE_DB_AUTO_UPDATE=falseand retry against a pre-downloaded DB (see Recipe 11). - "no Docker daemon" error? Use
registry:mode:grype registry:alpine:3.20.
Recipe 2: Scan a local filesystem directory
Goal: scan the unpacked rootfs of a custom system before flashing it.
Prerequisites: the rootfs at a known path, with a populated
/etc/os-release file (or pass --distro).
Steps:
grype dir:/srv/firmware/rootfs --distro debian:12 -o json > scan.json
Verify: jq '.matches | length' scan.json returns the match count.
If it fails:
- Warning "Unable to determine the OS distribution"? Pass
--distroto force it. - "no packages found"? Confirm package metadata directories exist
(
/var/lib/dpkg,/var/lib/rpm,/lib/apk/db,/var/db/pkg).
Recipe 3: Scan via SBOM (the syft + grype pipeline)
Goal: generate one SBOM in CI, scan it many times — fast and auditable.
Prerequisites: syft v1.0+ and grype both installed.
Steps:
# generate the SBOM once
syft myorg/myimage:tag -o json > sbom.json
# scan the SBOM (no daemon, no registry calls)
grype sbom:./sbom.json --fail-on high
# or pipe directly without writing to disk
syft myorg/myimage:tag -o json | grype --fail-on high
Verify: the SBOM-driven scan produces the same matches as a direct
image scan (compare jq '.matches | length' between both runs).
If it fails:
- "unable to parse SBOM"? Confirm Syft output is JSON:
syft ... -o json. Grype also accepts CycloneDX (-o cyclonedx-json) and SPDX (-o spdx-json). - Missing distro context? Re-run syft with the image to capture the
linux-releasecataloger output, or pass--distroon the grype side.
Recipe 4: Generate SARIF for GitHub code scanning
Goal: show vulnerability findings on the Security tab of a GitHub repo via Code Scanning.
Prerequisites: a GitHub repo with code scanning enabled; an actions workflow that builds the image.
Steps:
# .github/workflows/scan.yml
name: Vulnerability scan
on: [push]
permissions:
security-events: write
contents: read
jobs:
grype:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Install grype
run: curl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin
- name: Scan
run: grype app:${{ github.sha }} -o sarif --file results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Verify: the Code scanning alerts view in the repo's Security tab shows any matches grype produced.
If it fails:
- SARIF upload errors with "missing helpURI"? See GitHub issue #2874 — earlier releases left helpURI empty for some advisories. Upgrade to the latest grype to pick up the fix.
- "Resource not accessible by integration"? You're missing
permissions: security-events: writeon the job.
Recipe 5: Author and apply an ignore-list
Goal: silence a known-not-applicable false positive without breaking the rest of the scan.
Prerequisites: a .grype.yaml in the repo root, the CVE ID, and
ideally a reason that satisfies your audit team.
Steps:
# .grype.yaml
ignore:
- vulnerability: CVE-2024-99999
reason: "vendor confirmed not exploitable in our deployment topology"
package:
name: openssl
version: "3.0.*"
grype myimage:latest -c ./.grype.yaml --show-suppressed
Verify: the suppressed match shows up under "(suppressed)" in the
table view, and jq '.ignoredMatches[] | select(.match.vulnerability.id == "CVE-2024-99999")' scan.json finds it in the JSON output.
If it fails:
- All matches disappear? Your rule has no fields and matches everything — add at least one specific selector.
- Rule doesn't apply? Use
--show-suppressedand check the package name and version regex match exactly what grype detected.
Recipe 6: Fail the build on severity in CI
Goal: any High or Critical match should fail the pipeline.
Prerequisites: grype installed in the CI runner; image already built.
Steps:
grype "$IMAGE_TAG" --fail-on high --only-fixed -o json --file scan.json
case $? in
0) echo "no high/critical findings" ;;
2) echo "BLOCKED: high/critical findings — see scan.json"; exit 1 ;;
*) echo "scan errored"; exit 1 ;;
esac
Verify: an image you know has High vulnerabilities (e.g. an old
debian:9 base) returns exit 2.
If it fails:
- "discovered vulnerabilities at or above the severity threshold" but exit
code is
1? Check that the run wasn't aborted before the threshold evaluation — e.g. by a flag parse error.
Recipe 7: Custom output template
Goal: emit findings as Markdown for a Slack notifier.
Prerequisites: grype installed; a template file.
Steps:
cat > slack.tmpl <<'EOF'
{{- if .Matches -}}
*Vulnerability findings for {{ .Source.Target }}:*
{{ range .Matches }}
• `{{.Artifact.Name}}@{{.Artifact.Version}}` — *{{.Vulnerability.Severity}}* {{.Vulnerability.ID}}
{{- end }}
{{- else -}}
No vulnerabilities found.
{{- end }}
EOF
grype myimage:latest -o template -t ./slack.tmpl
Verify: the output is plain Markdown ready to POST to a Slack webhook.
If it fails:
- "function not defined" in template? Sprig's full library is available —
check spelling.
default,printf,lower,upper,replace,toJsonall work. - "expected an integer"? Use
{{ index .Vulnerability.Fix.Versions 0 }}rather than[0]indexing in Go templates.
Recipe 8: Scan an OCI tarball
Goal: scan an image that exists only as a .tar saved by Skopeo or
Podman.
Prerequisites: the OCI tarball on disk.
Steps:
# OCI archive (from podman save or skopeo copy --format oci)
grype oci-archive:./my-image.tar
# Docker save tarball
grype docker-archive:./my-image.tar
# OCI image-layout directory (e.g. from `skopeo copy ... oci:./layout`)
grype oci-dir:./layout
Verify: the scan completes without making any network calls (other than DB updates, if enabled).
If it fails:
- "unable to identify image source"? Confirm the archive is the format you
claimed — try the other prefix (
docker-archive:↔oci-archive:). - "no manifest found"? Ensure the tarball is not just layer blobs — it
must include
manifest.jsonorindex.json.
Recipe 9: Scan a directory but exclude paths
Goal: scan a giant repo while skipping vendored deps and test fixtures.
Prerequisites: glob patterns for paths to skip.
Steps:
grype dir:./project \
--exclude './**/node_modules/**' \
--exclude './**/vendor/**' \
--exclude './testdata/**'
Verify: the scan completes faster and the package count drops in the summary line.
If it fails:
- Patterns don't seem to apply? Use
**(doublestar) globs and quote them to prevent shell expansion. Test with-vvto see which paths grype is visiting.
Recipe 10: Set the DB cache location
Goal: put the DB on a fast SSD or share it across CI runners on a networked volume.
Prerequisites: a writable directory.
Steps:
export GRYPE_DB_CACHE_DIR=/mnt/fast/grype-db
mkdir -p "$GRYPE_DB_CACHE_DIR"
grype db update
grype alpine:3.20
Or in .grype.yaml:
db:
cache-dir: /mnt/fast/grype-db
Verify: grype db status reports the new path.
If it fails:
- "permission denied"? The runner's UID needs read+write on
cache-dir/v6/. - "no such file or directory"? Create the parent directory before the
first
db update.
Recipe 11: Mirror the DB on an air-gapped host
Goal: scan in environments with no internet access.
Prerequisites: an internet-connected host to fetch the DB; an internal mirror (HTTP server or filesystem path) reachable from the air-gapped host.
Steps:
# on internet-connected host
grype db list -o raw | jq -r '.[0].path' | xargs -I{} curl -OL "https://grype.anchore.io/databases/v6/{}"
# now copy the .tar.zst archive to the air-gapped host
# on air-gapped host
export GRYPE_DB_AUTO_UPDATE=false
grype db import ./vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst
# scan as normal (no network calls)
grype dir:/srv/app
For repeated mirroring, host the listing and tarballs on an internal HTTPS server and point grype at the listing URL:
db:
update-url: https://internal-mirror.corp/grype/v6/latest.json
ca-cert: /etc/ssl/certs/internal-ca.crt
Verify: grype db status shows the imported DB; subsequent scans run
without network access.
If it fails:
- "checksum mismatch on import"? Re-fetch the archive — partial download or corruption.
- "TLS handshake error"? The mirror's server certificate isn't trusted —
set
db.ca-certto point at the issuing CA.
Recipe 12: JSON parsing for downstream tooling
Goal: extract just (package, version, CVE, severity, fix) rows for a
spreadsheet.
Prerequisites: jq installed; grype JSON output.
Steps:
grype myimage:latest -o json | jq -r '
.matches[] |
[.artifact.name,
.artifact.version,
.vulnerability.id,
.vulnerability.severity,
(.vulnerability.fix.versions // [] | join("|"))]
| @csv'
Verify: output is CSV with one row per match.
If it fails:
jqerrors onnullfields? Use// ""defaults like the example above (every field that may be absent gets a default).
Recipe 13: Distro override for a directory scan
Goal: scan a directory whose /etc/os-release is missing or wrong.
Prerequisites: know the actual distro and version.
Steps:
grype dir:/srv/rootfs --distro alpine:3.20
grype dir:/srv/rhel-9-rootfs --distro rhel:9.4+eus
grype dir:/srv/sles-rootfs --distro sles:15.5
Verify: the warning "Unable to determine the OS distribution" no longer appears.
If it fails:
- "unknown distro"? Check the Admin Guide → Architecture for the full
supported distro list. The string after
--distrois the distro ID (debian,rhel,alpine, etc.) followed by-,:, or@and a version or codename.
Recipe 14: Apply VEX documents
Goal: consume vendor-issued OpenVEX or CSAF VEX documents to suppress matches that have been triaged as not-affected.
Prerequisites: an OpenVEX or CSAF VEX file from your vendor; grype installed.
Steps:
grype myimage:latest --vex ./vex.json --vex ./second-vex.json
Verify: matches that the VEX document marks as not_affected or
fixed move from matches to ignoredMatches in the JSON output. With
--show-suppressed, they appear under "(suppressed)" in the table.
If it fails:
- "VEX document not found"? Confirm the path; relative paths resolve from CWD.
- "unsupported VEX document format"? grype currently supports OpenVEX
(
@context: "https://openvex.dev/ns/...") and CSAF VEX (document.csaf_version). Other VEX dialects aren't supported. - Need to opt in to keeping
affectedmatches that VEX flags? Usevex-add: ["affected"]in.grype.yaml.
Recipe 15: Scan with a config file
Goal: apply a project-specific config without polluting environment variables.
Prerequisites: a .grype.yaml (or .grype/config.yaml) in the repo.
Steps:
# point at a specific file
grype myimage:latest -c ./.grype/ci-config.yaml
# or rely on the search path (./.grype.yaml is found automatically)
grype myimage:latest
# print the merged config for debugging
grype config
Verify: grype config shows your settings overriding the defaults.
If it fails:
- Settings ignored? Check
grype configto confirm which file was loaded — env vars can win over file values; flags always win over both. - YAML parse error? Run the file through
yq evalorpython -c "import yaml,sys; yaml.safe_load(sys.stdin)" < .grype.yamlto localize the syntax problem.
Recipe 16: Image platform override (multi-arch images)
Goal: scan the linux/arm64 variant of a multi-arch image from an
amd64 host.
Prerequisites: outbound access to the registry hosting the image.
Steps:
grype myorg/myimage:tag --platform linux/arm64
Verify: the JSON source.target.architecture field reads arm64.
If it fails:
- "no matching manifest" — confirm the platform string. Valid forms:
linux/amd64,linux/arm64,linux/arm/v7,windows/amd64, etc.
Recipe 17: Generate missing CPEs to broaden NVD matching
Goal: find vulnerabilities for binaries grype catalogs without an explicit CPE (e.g. some Go binaries, custom-built packages).
Prerequisites: grype installed; a target with packages that lack CPEs.
Steps:
grype mybuild:latest --add-cpes-if-none
Verify: the match count rises for binary-only artifacts; review for false positives because CPE inference is heuristic.
If it fails:
- Too many false positives? Pin to specific ecosystems via your matcher
config (see Admin Guide → Configuration Reference). For Java in
particular, set
match.java.using-cpes: false.
6. Troubleshooting
Diagnostic decision tree
- Does
grype versionprint a version? If not, the binary is missing from$PATH— re-check the install location. - Does
grype db statussayvalid? If not, rungrype db delete && grype db update. If it still fails, see the DB-related entries below. - Does the scan get past "Loaded image"? If not, the source can't be read — see "image source" entries.
- Does the matcher run but produce zero matches when you expect some?
Try
--add-cpes-if-none,--by-cve, or pass an explicit--distro. - Is the exit code 2 when you didn't expect it? That's
--fail-onfiring. Drop or raise the threshold, or audit the matches that tipped it over.
Error: db could not be loaded: checksum mismatch
Cause: the local DB tarball was corrupted on download (network drop, partial write) or modified after install. Detected on the curator's validation pass.
Solution:
- Run
grype db deleteto wipe the cache. - Run
grype db updateto re-download. - If it persists, set
GRYPE_DB_VALIDATE_CHECKSUM=false(NOT recommended for production) to confirm whether checksum is the issue.
Prevention: scope your CI cache key to include the DB schema version so stale artifacts don't poison rebuilds.
Error: failed to load vulnerability db: ... import.json
Cause: the DB cache is missing its import-metadata file, usually because
a partial install was interrupted (Ctrl-C during grype db update). The
notification reads Unable to find database import metadata and is
emitted by validateDBLoad() in cmd/grype/cli/commands/root.go.
Solution:
grype db deletegrype db update- Retry the scan.
Prevention: don't kill grype mid-update on shared volumes.
Error: Unable to determine the OS distribution of some packages
Cause: the target has dpkg/rpm/apk packages but no recognizable
/etc/os-release, so the OS matchers can't run. Common with stripped
filesystem images and bespoke base images.
Solution: pass --distro debian:11 (or whatever the actual distro
is). For SBOMs, regenerate with syft --select-catalogers +linux-release
to ensure distro detection runs.
Prevention: keep /etc/os-release in your minimal images — it's a
few hundred bytes and unlocks correct vulnerability matching.
Error: no Docker daemon
Cause: the default scheme tried to reach Docker first; there's no socket. Common in CI runners using BuildKit-only setups or Kaniko.
Solution: prefix your target with registry: (no daemon required) or
set GRYPE_DEFAULT_IMAGE_PULL_SOURCE=registry:
grype registry:myorg/myimage:tag
Prevention: in containerized CI jobs, always default to registry mode so daemon presence isn't a precondition.
Error: attempted CPE match against package with no CPEs
Cause: internal trace-level message. A matcher tried CPE-based search on a package that the cataloger didn't tag with any CPEs (typical for language-ecosystem packages where CPE is not natural).
Solution: this is normally a debug message, not an error. If you
want CPE matches for those packages, run with --add-cpes-if-none. If
you're seeing the message at WARN level, confirm your matcher config
doesn't force CPE for the wrong ecosystem.
Prevention: none needed — the message is informational unless it correlates with missing matches you expected.
Error: discovered vulnerabilities at or above the severity threshold
Cause: --fail-on SEVERITY matched at least one finding. Exit code is
2. The error is grypeerr.ErrAboveSeverityThreshold and is the intended
behavior — not a bug.
Solution: review scan.json's matches[] array for the offenders.
Either fix the underlying packages, add ignore rules, or relax the
threshold.
Prevention: keep --fail-on at the level your team has agreed to
gate on. high is a reasonable default; critical is the floor.
Error: unsupported output format "<X>"
Cause: typo or deprecated format. The supported set is table,
json, cyclonedx-json, cyclonedx-xml, sarif, template.
Solution: correct the spelling. Note that embedded-cyclonedx-vex-json
is deprecated and resolves to cyclonedx-json (with a deprecation
warning).
Prevention: lint your .grype.yaml output: list against the
supported formats before merging configs.
Error: must specify at least one vulnerability ID (db search vuln)
Cause: grype db search vuln with no args.
Solution: pass at least one ID — grype db search vuln CVE-2021-44228.
Prevention: check the --help for any unfamiliar subcommand.
Error: provider not found: "<name>"
Cause: you filtered db search to a provider (--provider <name>)
that isn't present in the local DB.
Solution: run grype db providers to see the actual list. Provider
names are lowercase (alpine, nvd, github, redhat, ubuntu, etc.).
Prevention: drive provider lists from grype db providers -o json | jq -r '.[].name' rather than hard-coding strings.
Issue: false positive — Flask / Werkzeug from Python ecosystem
Cause: see GitHub issue #3354 — the Python ecosystem matcher matches via PURL on certain Werkzeug/Flask CVEs even when the installed version is patched, because the advisory range is broad.
Solution: add a targeted ignore in .grype.yaml:
ignore:
- vulnerability: CVE-2024-34069
package:
name: werkzeug
type: python
reason: "false positive — see anchore/grype#3354"
Prevention: track upstream issue #3354 for a permanent fix, then remove the ignore.
Issue: false positive — GHSA-pxwf-m68q-gmw8 (CVE-2023-2004)
Cause: see GitHub issue #3250 — fuzzy package-name matching in some ecosystems produces this false positive against unrelated packages.
Solution: add an ignore rule scoped to the package name and ecosystem that's misfiring; subscribe to the upstream issue for a fix.
Issue: --only-fixed not effective for Python packages
Cause: see GitHub issue #3312 — the fix-state filter doesn't correctly filter all Python ecosystem advisories.
Solution: combine --only-fixed with --ignore-states wont-fix,unknown
to compensate, or filter in your downstream tooling on
.matches[].vulnerability.fix.state == "fixed".
Issue: SARIF output missing helpUri
Cause: see GitHub issue #2874 (closed in newer releases) — earlier
versions left the SARIF result.helpUri field empty for some advisories.
Solution: upgrade to the latest grype release; the fix is included.
Prevention: pin grype versions in CI and bump them deliberately on a schedule.
Issue: false positive on Node.js 22 and CVE-2026-21710
Cause: see GitHub issue #3359 (closed) — the Node.js advisory range overlapped with already-fixed versions.
Solution: upgrade to a release after the issue's fix landed.
Issue: scan hangs on a large image
Cause: typically the cataloger phase reading a deep filesystem with many small files (Java archives are a common culprit because of the indexed/unindexed archive walk).
Solution: profile with -vv. To skip Java archive deep-indexing:
grype myimage:latest \
--search-include-indexed-archives=false \
--search-include-unindexed-archives=false
Or limit memory pressure on the runner.
Prevention: for very large monorepo scans, prefer the SBOM-first pipeline (Recipe 3) — generate the SBOM once with syft and scan it many times.
Issue: registry pull fails with 401/403
Cause: grype is anonymously pulling from a private registry.
Solution: add credentials via GRYPE_REGISTRY_AUTH_* env vars or in
.grype.yaml:
registry:
auth:
- authority: ghcr.io
token: ${GHCR_TOKEN}
- authority: my-private.registry.local
username: ci-bot
password: ${REGISTRY_PASSWORD}
The username, password, and token fields are marked sensitive and
redacted from grype's own logs by internal/redact.
Prevention: standardize on env vars in CI; never commit auth blocks with raw credentials.
Issue: grype completion doesn't tab-complete image names
Cause: completion is wired up but Docker isn't reachable to enumerate
local images — dockerImageValidArgsFunction falls back to the default
behavior.
Solution: confirm docker images works; the completion uses the same
client. If you don't use Docker, this is expected.
7. FAQ
Q: How is grype different from syft?
Syft generates SBOMs (a list of packages); grype scans for vulnerabilities. They're sister tools from the same project — grype imports syft as a library to do its cataloging step. A typical pipeline: syft generates an SBOM once, grype scans the SBOM repeatedly.
Q: Where is the vulnerability database stored?
~/.cache/grype/db/<schema-version>/ by default — currently
~/.cache/grype/db/6/ for schema v6. Override with GRYPE_DB_CACHE_DIR
or the db.cache-dir config key.
Q: How often is the database rebuilt?
Anchore publishes new builds multiple times per day. By default grype
checks for updates on every scan, but caches the answer for 2 hours
(max-update-check-frequency). Set db.auto-update: false in your
config to pin a known-good DB.
Q: What's the difference between --only-fixed and --ignore-states wont-fix,not-fixed,unknown?
--only-fixed is a shorthand that adds the equivalent of
--ignore-states wont-fix,not-fixed,unknown. They're functionally the
same. Use whichever is clearer in your config.
Q: How do I exclude a specific path from a directory scan?
Use --exclude PATTERN (repeatable). The pattern is a doublestar glob —
quote it to keep the shell from expanding it:
--exclude './**/node_modules/**'.
Q: Can grype scan Windows containers?
Yes — grype itself runs on Windows, and the Windows distro type is
handled (it appears in the Type enum). However, Windows containers in
practice use very different package metadata and the matcher coverage is
narrower than for Linux distros. The Microsoft Security Response Center
(MSRC) matcher handles KB-style advisories.
Q: What's a "fix channel" (e.g. rhel:9.4+eus)?
A fix channel is a distinct vulnerability/fix feed for a distro variant.
Red Hat's Extended Update Support (EUS) is the canonical example: RHEL
9.4 with EUS gets a different stream of CVE fixes than non-EUS 9.4. Grype
honors the +eus suffix on the distro string and applies the EUS feed
when present.
Q: What's the difference between cyclonedx-json and embedded-cyclonedx-vex-json?
They're identical now — embedded-cyclonedx-vex-json is the legacy name,
kept for backwards compatibility, and emits a deprecation warning. Use
cyclonedx-json. The embedded-* aliases will be removed at v1.0.
Q: What's --by-cve for?
By default each match is keyed by the advisory namespace (NVD, GHSA,
RHSA, etc.). --by-cve collapses everything to the canonical CVE
identifier so a single CVE doesn't appear multiple times for different
data sources. Useful when downstream tooling expects per-CVE rows.
Q: What ecosystems does grype catalog?
apk, deb, rpm, portage, pacman; Java (Maven JARs/WARs/EARs), JavaScript
(npm), Python (PyPI), Ruby (RubyGems), Go modules, .NET, Rust crates,
Erlang/Elixir Hex, Bitnami packages, MSRC. The cataloger code lives in
syft; the matcher code is in grype/matcher/.
Q: How do I make scans deterministic for a CI cache hash?
Pin both grype version and DB version. Pin grype with a specific install
tag (curl ... | sh -s -- v0.96.1). Pin the DB by setting
GRYPE_DB_AUTO_UPDATE=false and using grype db import ./pinned.tar.zst
in your prebuild step.
Q: Does grype phone home?
Only for two purposes: (1) DB updates from grype.anchore.io (disable
with db.auto-update: false), and (2) an application-update check (one
GET to toolbox-data.anchore.io/grype/releases/latest/VERSION — disable
with check-for-app-update: false). No telemetry, no analytics.
Q: Can I use grype offline?
Yes — see Recipe 11. grype db import accepts a local file or HTTPS URL.
Set db.auto-update: false to prevent any network calls during scans.
Q: What's the relationship between schema versions and grype releases?
The DB schema version (currently 6) and the grype release version
(currently 0.96.x) are independent. A grype release is built against a
specific schema version; the binary contains a Supported DB Schema:
line in grype version. When the schema bumps, all consumers must
update grype before the new DB will load — see
schema/grype/db/README.md in the repo for the SchemaVer rules.
Q: Why is grype db search vuln v6-only?
The query path uses v6 GORM models and SQL joins. Earlier schemas had a different on-disk layout and didn't expose the same indices.
Q: Where do EPSS and KEV data come from?
Schema v6+ embeds Exploit Prediction Scoring System (EPSS) scores and
CISA's Known Exploited Vulnerabilities (KEV) catalog as additional
columns on advisory records. They're surfaced in the JSON output and in
grype db diff (--epss, --kev selectors).
Q: How do I write a regex in an ignore rule?
The vulnerability, package.name, package.upstream-name, and
package.location fields all accept Go regex syntax. Anchor your
patterns or you'll match more than you intended:
ignore:
- vulnerability: ^CVE-2024-(0001|0002)$
package:
name: ^lib(crypto|ssl).*$
Q: Where do I report false positives?
Open an issue at https://github.com/anchore/grype/issues with the
package name, ecosystem, version, the advisory ID, and a minimal
reproducer (a small Dockerfile or SBOM is ideal). Many false-positive
issues are tracked there — see #3354 (Flask/Werkzeug), #3250 (GHSA),
#3312 (Python --only-fixed).
Q: How do I scan an OCI image directly from a registry without pulling first?
Use the registry: scheme — grype talks directly to the registry HTTP API
without a local daemon: grype registry:ghcr.io/anchore/grype:latest.
Auth via GRYPE_REGISTRY_AUTH_* or .grype.yaml.
Q: What does the message "EOL distro" mean?
When the alerts subsystem is enabled, grype warns when scanned packages
come from a distro release that has reached end-of-life — vulnerability
data may be stale or incomplete. The detection happens in
VulnerabilityMatcher when Alerts.EnableEOLDistroWarnings is set.
Toggle via alerts.enable-eol-distro-warnings in config.