How Trivy's Database Update Mechanism Works: A Technical Deep Dive into the Source Code

Trivy uses a lightweight DB client in pkg/db/db.go that checks local cache freshness against OCI registry timestamps and only downloads new vulnerability data when the database is missing, schema-incompatible, or past its next-update time.

Trivy’s database update mechanism balances freshness with performance by maintaining a local SQLite database cached in ~/.cache/trivy and only pulling updates from OCI registries when necessary. According to the aquasecurity/trivy source code, the update flow is orchestrated by the Client struct in pkg/db/db.go, which coordinates metadata checks, schema validation, and OCI artifact downloads. This architecture ensures that repeated scans do not trigger redundant network requests while keeping vulnerability definitions current.

Core Architecture: The DB Client

The update mechanism centers on the Client struct defined in pkg/db/db.go. When instantiated via NewClient, it initializes three critical components:

  • Cache directory: Where trivy.db and metadata.json are stored (default: ~/.cache/trivy)
  • Metadata client: Handles metadata.json read/write operations via metadata.NewClient
  • Repository endpoints: Default OCI registries including ghcr.io/aquasecurity/trivy-db:<schema> (Primary) and mirror.gcr.io/aquasec/trivy-db:<schema> (Mirror)
// From pkg/db/db.go - NewClient constructor
client := db.NewClient(db.Dir("/tmp/trivy-cache"), false)

Determining When Updates Are Required

The NeedsUpdate method (lines 104-166 in pkg/db/db.go) implements a four-phase validation to decide whether to trigger a download:

  1. Existence checks: Verifies trivy.db and metadata.json are present
  2. Schema compatibility: Compares embedded schema version against the running Trivy binary's requirements
  3. Timestamp validation: Examines NextUpdate (predicted expiry) and DownloadedAt (last successful pull)
  4. CLI override flags: Respects --skip-db-update and --download-db-only from pkg/flag/db_flags.go
// Conceptual flow from pkg/db/db.go
needsUpdate, err := client.NeedsUpdate(ctx, schemaVersion, skipUpdateFlag)

Fast-Path Optimization with isNewDB

To prevent rapid re-downloads, the isNewDB helper function (lines 72-84) implements a 1-hour cooldown period. If DownloadedAt indicates the database was fetched within the last hour, or if NextUpdate is still in the future, Trivy skips the update regardless of other conditions. This "fast-path" check runs before any network operations occur.

Downloading the Database via OCI Artifacts

When NeedsUpdate returns true, the Download method invokes downloadDB to fetch the database as an OCI artifact. Trivy pulls the layer with media type application/vnd.aquasec.trivy.db.layer.v1.tar+gzip from the configured registries using the abstraction in pkg/oci/artifact.go.

The download process:

  • Attempts the GHCR repository first (ghcr.io/aquasecurity/trivy-db)
  • Falls back to Google Container Registry mirror if unavailable
  • Validates the downloaded tarball against the expected schema version
  • Writes the file to the cache directory specified during client initialization
// From the Go API example - Download execution
err := client.Download(ctx,
    db.Path(client.DBDir()),
    types.RegistryOptions{})

Metadata Management and Timestamp Tracking

After successful download, updateDownloadedAt updates the metadata.json file (managed in pkg/db/metadata/) to record the current timestamp. This file also stores NextUpdate, which predicts when the next database refresh will be available based on the publisher's build cycle. The metadata layer ensures that Trivy can operate offline while knowing exactly when to check for freshness again.

CLI Flags and Configuration Options

User control over the update mechanism is implemented in pkg/flag/db_flags.go. The flag group parses the following options before they reach the DB client:

  • --skip-db-update: Forces Trivy to use the existing cached database regardless of freshness
  • --download-db-only: Exits after updating the database without performing any scans
  • --db-repository: Allows specifying a custom OCI repository URL instead of the defaults

# Skip updates for air-gapped environments

trivy image --skip-db-update alpine:3.18

# Update database without scanning

trivy image --download-db-only

Practical Implementation Examples

Command-Line Usage

When running Trivy from the CLI, the update check happens automatically during scanner initialization:


# Normal execution - updates if NextUpdate has passed

trivy image alpine:3.18

# Force skip (useful in CI/CD with pre-cached DBs)

trivy fs --skip-db-update ./my-project

# Database maintenance mode

trivy image --download-db-only

Programmatic Go API

For custom tooling, you can invoke the update mechanism directly using the pkg/db package:

package main

import (
	"context"
	"log"

	"github.com/aquasecurity/trivy/pkg/db"
	"github.com/aquasecurity/trivy/pkg/types"
)

func main() {
	// Initialize client with cache directory
	client := db.NewClient(db.Dir("/tmp/trivy-cache"), false)
	ctx := context.Background()

	// Check if update is required (schema version "2", skip=false)
	needs, err := client.NeedsUpdate(ctx, "2", false)
	if err != nil {
		log.Fatalf("failed to check DB: %v", err)
	}

	if needs {
		// Download from default OCI registries
		if err := client.Download(ctx,
			db.Path(client.DBDir()),
			types.RegistryOptions{}); err != nil {
			log.Fatalf("failed to download DB: %v", err)
		}
		log.Println("Database updated successfully")
	} else {
		log.Println("Database is current (cached)")
	}
}

Summary

  • Trivy's database update mechanism relies on the Client struct in pkg/db/db.go to coordinate between local cache state and remote OCI registries.
  • Updates are conditional: The NeedsUpdate function only returns true when the database is missing, schema-incompatible, or past its NextUpdate timestamp (bypassing the 1-hour cooldown).
  • OCI distribution: Databases distribute as versioned artifacts via ghcr.io/aquasecurity/trivy-db and Google Container Registry mirrors, pulled only when necessary.
  • User control: Flags in pkg/flag/db_flags.go provide explicit --skip-db-update and --download-db-only options for CI/CD and air-gapped environments.
  • Metadata tracking: metadata.json (managed by pkg/db/metadata/) stores DownloadedAt and NextUpdate timestamps to optimize update frequency.

Frequently Asked Questions

How often does Trivy check for database updates?

Trivy checks for updates on every execution, but the actual download only occurs if the cached database is older than the NextUpdate timestamp or if the schema version mismatches. Additionally, the isNewDB logic prevents re-downloads within 1 hour of the last successful fetch, protecting against redundant network calls during rapid successive scans.

Can I run Trivy completely offline without database updates?

Yes. Pass the --skip-db-update flag to force Trivy to use the existing cached database in ~/.cache/trivy. This is essential for air-gapped environments where the scanner cannot reach the OCI registries at ghcr.io or gcr.io. Ensure you pre-populate the cache directory with a valid trivy.db and metadata.json pair.

What happens if the primary GitHub Container Registry is unavailable?

Trivy automatically falls back to the Google Container Registry mirror (mirror.gcr.io/aquasec/trivy-db) if the primary GHCR endpoint fails or times out. This redundancy is hardcoded in the default repository list within pkg/db/db.go, ensuring high availability for vulnerability database distribution.

How do I use a custom database repository instead of the official ones?

Specify the --db-repository flag with your custom OCI registry URL. Trivy will attempt to pull the database layer from your specified repository using the same application/vnd.aquasec.trivy.db.layer.v1.tar+gzip media type. Ensure your custom repository follows the same schema versioning and layer format as the official trivy-db releases.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →