How Trivy Parallel Scanning Boosts Performance: A Deep Dive into the Generic Pipeline
Trivy accelerates vulnerability scans by orchestrating a configurable pool of worker goroutines through a generic pipeline implementation that processes independent tasks concurrently while respecting context.Context cancellation.
Trivy parallel scanning is the core mechanism that allows Aqua Security's open-source scanner to analyze container images, Kubernetes manifests, and filesystem artifacts at scale. According to the aquasecurity/trivy source code, the tool achieves this through a reusable pipeline architecture defined in pkg/parallel/pipeline.go, which coordinates work distribution across multiple CPU cores while preventing resource exhaustion through configurable limits and semaphore-based throttling.
Configuring Parallelism with the --parallel Flag
Trivy exposes scan concurrency via the --parallel CLI flag, defined in pkg/flag/scan_flags.go. The flag accepts an integer specifying the number of goroutines (default: 5) and supports auto-detection when set to 0.
ParallelFlag = Flag[int]{
Name: "parallel",
ConfigName: "scan.parallel",
Default: 5,
Usage: "number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism",
TelemetrySafe: true,
}
When parsed, the value populates flag.Options.Parallel and propagates downstream to individual scanners. Setting --parallel 0 triggers automatic configuration based on runtime.GOMAXPROCS, allowing Trivy to match available CPU capacity without manual tuning.
The Generic Pipeline Architecture
All major Trivy scanners rely on a type-safe Pipeline[T, U] struct in pkg/parallel/pipeline.go. This generic implementation creates:
- An input channel (
itemCh) feeding work items of typeT - A configurable number of workers (
numWorkers) consuming fromitemCh - An output channel (
results) collecting processed values of typeU - A final
onResultconsumer running in the main goroutine for lock-free aggregation
type Pipeline[T, U any] struct {
numWorkers int
items []T
onItem func(context.Context, T) (U, error)
onResult func(U) error
progress bool
}
The pipeline uses errgroup.WithContext for automatic error propagation and cancellation. If any worker returns an error, the shared context cancels immediately, aborting remaining goroutines while preserving the first encountered error for deterministic failure handling.
Where Parallel Scanning Is Applied
Trivy applies parallel scanning across multiple subsystems, each adapting the pipeline to specific resource constraints:
Kubernetes Scanner
The scanner in pkg/k8s/scanner/scanner.go creates a pipeline where each worker executes s.scanVulns on individual container images referenced in cluster manifests. Notably, when using the filesystem cache backend (cache.TypeFS), Trivy forces workers = 1 to avoid bbolt lock contention.
Image Artifact Analysis
In pkg/fanal/artifact/image/image.go, parallel processing handles:
- Uncompressed size calculation across layers
- OS detection spanning multiple layer keys
- Per-layer content analysis
Filesystem and VM Artifacts
For I/O-heavy operations, Trivy uses semaphore.New(a.artifactOption.Parallel) to cap concurrent filesystem operations, preventing resource overload during layer extraction or directory walks.
Default Walk Utility
The pkg/parallel/walk.go file defines defaultParallel = 5 as a fallback for filesystem traversal when no explicit value is provided.
Cancellation and Error Handling
The pipeline's errgroup integration ensures cooperative cancellation. When Pipeline.Do(ctx) executes:
- Workers process items from
itemChuntil exhaustion or context cancellation - Results stream to
onResultin the main goroutine, eliminating need for additional synchronization primitives - The first error triggers immediate context cancellation across all workers
Doreturns the initial error, maintaining predictable failure semantics
This design prevents partial result corruption while allowing scanners to fail fast when encountering critical errors.
Practical Implementation Examples
Run a scan with 8 concurrent workers from the CLI:
trivy image --parallel 8 nginx:latest
Programmatically construct a parallel pipeline using Trivy's internal utilities:
package main
import (
"context"
"fmt"
"github.com/aquasecurity/trivy/pkg/parallel"
)
type layer string
func main() {
ctx := context.Background()
layers := []layer{"layerA", "layerB", "layerC"}
p := parallel.NewPipeline(
4, // numWorkers
false, // disable progress bar
layers, // items to process
func(_ context.Context, l layer) (string, error) {
// Analyze container layer
return fmt.Sprintf("processed %s", l), nil
},
func(res string) error {
fmt.Println(res) // Safe aggregation in main goroutine
return nil
},
)
if err := p.Do(ctx); err != nil {
panic(err)
}
}
Adjust parallelism programmatically when initializing the scanner:
opts := flag.Options{
Parallel: 10, // 10 workers for this scan
}
scanner := scanner.NewScanner("my-cluster", runner, opts)
report, err := scanner.Scan(context.Background(), artifacts)
Summary
- Trivy parallel scanning leverages a generic
Pipeline[T, U]type inpkg/parallel/pipeline.goto coordinate worker goroutines across vulnerability scans. - The
--parallelflag (default: 5) controls concurrency, with 0 enabling auto-detection viaruntime.GOMAXPROCS. - Scanners automatically reduce workers to 1 when using the filesystem cache to prevent database lock contention.
- The
errgroup-based implementation provides automatic cancellation and deterministic error handling. - Semaphores cap I/O concurrency for filesystem and VM artifact processing, preventing resource exhaustion.
Frequently Asked Questions
How does Trivy handle errors during parallel scanning?
Trivy uses errgroup.WithContext inside pkg/parallel/pipeline.go to manage worker lifecycles. When any worker returns an error, the shared context cancels immediately, causing all other goroutines to abort. The Pipeline.Do method returns the first encountered error, ensuring scans fail fast without producing partial results.
Can I disable parallel scanning in Trivy?
Yes, set --parallel 1 to force single-threaded execution. Additionally, Trivy automatically disables parallelism (forcing one worker) when using the filesystem cache backend for Kubernetes scanning, as implemented in pkg/k8s/scanner/scanner.go, to avoid bbolt database lock contention.
What is the default parallelism value in Trivy?
The default value is 5, defined in pkg/flag/scan_flags.go and mirrored in pkg/parallel/walk.go as defaultParallel. You can override this with the --parallel flag or set it to 0 to enable auto-detection based on available CPU cores via runtime.GOMAXPROCS.
Which Trivy operations benefit most from parallel scanning?
Container image layer analysis in pkg/fanal/artifact/image/image.go and Kubernetes manifest scanning in pkg/k8s/scanner/scanner.go see the greatest performance gains, as these involve independent analysis of multiple layers or images. Filesystem scanning uses semaphores to balance parallelism with I/O constraints.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →