How do you keep Trivy 0.74 vulnerability scans working and current in an air-gapped CI network that cannot reach mirror.gcr.io or ghcr.io?
answer
- copy the artifacts, keep the tag
- point both DB flags inward
- overriding drops the defaults
- the first run cannot skip
- freshness becomes your job
basics
~20 sMirror trivy-db:2 and trivy-java-db:1 into an internal registry and point --db-repository and --java-db-repository at it, or copy trivy.db and metadata.json into the cache and scan with --skip-db-update. Either way, refreshing the copy is now your job.
solid answer
~40 sThere are two patterns. The preferred one: a scheduled job copies `trivy-db:2` and `trivy-java-db:1` — OCI artifacts with their own layer media types — into a registry the runners can reach, and scans set `--db-repository` and `--java-db-repository` to it. Setting a flag replaces the public defaults, so nothing reaches outward, and Trivy keeps its normal refresh logic. The fallback: on a connected host run `trivy image --download-db-only` with `--cache-dir`, carry `trivy.db` and `metadata.json` into `<cache>/db/` (and `trivy-java.db` into `java-db/`), and scan with `--skip-db-update` and `--skip-java-db-update`; a first run without those files fails. Add `--offline-scan` so JAR identification does not call Maven Central, and alert on the DB's age — a frozen database passes every gate while missing every new CVE.
code
bash · 7 linesexport TRIVY_CACHE_DIR=/var/cache/trivy
trivy image \
--db-repository registry.example.internal/security/trivy-db:2 \
--java-db-repository registry.example.internal/security/trivy-java-db:1 \
--offline-scan \
--skip-version-check --disable-telemetry \
registry.example.internal/payments/api:1.8.2go deeper
Recall that an offline scanner needs its databases supplied internally, and that --skip-db-update only works once a database is already in the cache.
Explain the two patterns, registry mirror versus a hand-populated cache, and the flags each needs: the two repository flags, the two skip flags and --offline-scan.
Show the failure modes: defaults replaced not appended, no fallback on denied, non-standard media types, a missing DownloadedAt, and a frozen DB that quietly under-reports.
Decide who owns the mirror's refresh cadence and staleness alerting across every isolated runner, and how reports record which database build they were judged against.
## What an isolated runner is missing A Trivy scan needs more than the binary. For vulnerability scanning it needs `trivy-db` (compiled advisories) and, whenever it meets a JAR, `trivy-java-db` (an index that identifies JARs). Both are **OCI artifacts** whose 0.74 defaults are `mirror.gcr.io/aquasec/...` first and `ghcr.io/aquasecurity/...` second. Java identification may also call **Maven Central**, the optional VEX Hub repository is fetched from GitHub, and the update check contacts `check.trivy.dev`. An air-gapped runner must have every one of these answered internally or switched off. ## Pattern 1: an internal registry mirror This is the pattern to prefer, because Trivy's own refresh logic keeps working. 1. On a host with outbound access, copy `ghcr.io/aquasecurity/trivy-db:2` and `ghcr.io/aquasecurity/trivy-java-db:1` into your registry with a registry tool such as ORAS or crane, **keeping the schema tags**. 2. Run that copy on a schedule. The mirror is now the only source of freshness. 3. Point scans at it with `--db-repository` and `--java-db-repository`, or `TRIVY_DB_REPOSITORY` and `TRIVY_JAVA_DB_REPOSITORY`. 4. Authenticate the way Trivy authenticates to any private registry: `trivy registry login`, Docker credentials, or `TRIVY_USERNAME` and `TRIVY_PASSWORD`. Details that bite: - **Setting the flag replaces the defaults.** The public repositories are not appended behind yours; list them explicitly if you want them, which in an air-gapped network you do not. - **Fallback is narrow.** A second listed repository is tried only after a temporary error (429, 5xx) or `BLOB_UNKNOWN`; a denied or unauthorised response ends the attempt. Two internal mirrors give resilience; a wrong credential does not fail over. - **Media types are non-standard.** The layers are `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip` and `application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip`; a proxy that accepts only container-image types will reject them. - **No tag means the schema tag.** A repository given without a tag gets the schema version appended, never `latest`. ## Pattern 2: populating the cache by hand When no registry is available, carry the files in: 1. On a connected host, `trivy image --cache-dir . --download-db-only` writes `trivy.db` and `metadata.json` into the `db/` subdirectory of that cache (and `--download-java-db-only` does the same for the Java index; the two flags cannot be combined in one run). 2. Place them in the runner's cache: `<cache>/db/trivy.db` and `<cache>/db/metadata.json`, and `<cache>/java-db/trivy-java.db` with its own `metadata.json`. 3. Scan with `--skip-db-update` and `--skip-java-db-update`. The guard rails are in the source: with no DB present, `--skip-db-update` fails on the first run; with an old schema it fails too; and a DB extracted from an `oras pull` archive carries no `DownloadedAt`, so without the skip flag Trivy warns that the DB may be corrupted and tries to re-download it. ## The other outbound calls | Call | How to handle it offline | |---|---| | Maven Central lookups for JARs | `--offline-scan`; it does not affect the databases, and some dependencies may be skipped | | VEX Hub, when using `--vex repo` | self-host a copy, or `--skip-vex-repo-update` | | version check and telemetry | `--skip-version-check` and `--disable-telemetry`, both needed | | misconfiguration checks bundle | handled by IaC scanning; the binary embeds a fallback copy | Running one Trivy server that holds the database for many clients is another topology, and keeping the cache between CI jobs is a pipeline concern; both still need a fresh database from somewhere. ## Freshness is now an operational duty An air-gapped scanner never fails because its data is old; it reports fewer findings. Make staleness visible: - Read `UpdatedAt` from `trivy version --format json` on the runner, or from `metadata.json`, and alert when it exceeds your tolerance. - Record the DB's `UpdatedAt` beside each stored report, so an auditor can tell which advisories a pass was judged against. - Re-scan released images whenever the mirror refreshes, not only on rebuild. ## Checklist for a new isolated runner 1. The internal registry serves `trivy-db:2` and `trivy-java-db:1`, refreshed on a schedule, and passes the non-standard layer media types. 2. Runners have credentials for that registry and nothing else outbound. 3. `--db-repository` and `--java-db-repository` (or their environment variables) are set in shared configuration, not per job. 4. `--offline-scan`, `--skip-version-check` and `--disable-telemetry` are set, and `--vex repo` is either self-hosted or not used. 5. A first scan on a clean cache succeeds, which proves the mirror path end to end instead of a leftover cache. 6. An alert fires when `UpdatedAt` is older than the agreed tolerance. A failure at step 5 is cheap; the same failure discovered during an incident, when nobody can tell whether the scanner saw the new advisory, is not.
- The mirror rejects Trivy with an unauthorised error. Will Trivy try the next listed repository?No. Trivy moves on only after a temporary error such as 429 or a 5xx, or a `BLOB_UNKNOWN`; a denied or unauthorised response fails the download and points to the troubleshooting page. Fix the credentials with `trivy registry login` or `TRIVY_USERNAME` and `TRIVY_PASSWORD`, rather than relying on fallback.
- Why did the pre-populated cache trigger a 'DB may be corrupted' warning?Files extracted from an `oras pull` archive carry no `DownloadedAt` in `metadata.json`. Without `--skip-db-update`, Trivy cannot tell a hand-copied DB from a corrupted copy, warns, and tries to re-download, which fails offline. Scanning with `--skip-db-update` tells it the copy is deliberate.
- A caching proxy serves images fine but the DB pull fails. What do you check first?The layer media type. `trivy-db` uses `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip`, not a container-image layer type, and Trivy's documentation warns that proxies and mirrors must pass it through. A proxy that filters to image media types breaks the pull.
saying these in an interview costs you the question
- Set --skip-db-update on a fresh runner and Trivy scans with what it has
- With --db-repository set, Trivy still falls back to ghcr.io if the mirror is down
- --offline-scan stops Trivy downloading its vulnerability database
- Once mirrored, the database stays current on its own
- Mirror the DB under a latest tag so Trivy always gets the newest
- Copying trivy.db alone is enough; metadata.json is optional