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

> Explore Trivy's database update mechanism. Learn how the lightweight DB client efficiently downloads new vulnerability data by checking OCI registry timestamps and local cache freshness.

- Repository: [Aqua Security/trivy](https://github.com/aquasecurity/trivy)
- Tags: internals
- Published: 2026-03-23

---

**Trivy uses a lightweight DB client in [`pkg/db/db.go`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/pkg/db/db.go). When instantiated via `NewClient`, it initializes three critical components:

- **Cache directory**: Where `trivy.db` and [`metadata.json`](https://github.com/aquasecurity/trivy/blob/main/metadata.json) are stored (default: `~/.cache/trivy`)
- **Metadata client**: Handles [`metadata.json`](https://github.com/aquasecurity/trivy/blob/main/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)

```go
// 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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/pkg/flag/db_flags.go)

```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`](https://github.com/aquasecurity/trivy/blob/main/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

```go
// 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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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

```bash

# 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:

```bash

# 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:

```go
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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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`](https://github.com/aquasecurity/trivy/blob/main/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.