Trivy can scan multi-architecture container images, but it’s not doing what you think it’s doing when you run trivy image myimage:latest.
Let’s see it in action. Imagine you’ve built a simple Go application that prints "Hello from ARM!" or "Hello from AMD64!".
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("Hello from %s!\n", runtime.GOARCH)
}
You build this for both amd64 and arm64 and push them to a registry.
# Build for amd64
GOOS=linux GOARCH=amd64 go build -o myapp-amd64
docker build -t myregistry/myapp:amd64-v1 -f Dockerfile.amd64 .
docker push myregistry/myapp:amd64-v1
# Build for arm64
GOOS=linux GOARCH=arm64 go build -o myapp-arm64
docker build -t myregistry/myapp:arm64-v1 -f Dockerfile.arm64 .
docker push myregistry/myapp:arm64-v1
# Create a multi-arch manifest
docker manifest create myregistry/myapp:latest \
myregistry/myapp:amd64-v1 \
myregistry/myapp:arm64-v1
docker manifest push myregistry/myapp:latest
Now, if you run trivy image myregistry/myapp:latest on an amd64 machine, Trivy doesn’t magically run the scan on both architectures. Instead, it consults the image manifest for myregistry/myapp:latest. This manifest is just a JSON file listing the available architectures and the digests of the actual image layers for each. Your Docker client (or the registry when you pull) uses this manifest to pick the correct image for your current architecture. Trivy does the same. When you run trivy image myregistry/myapp:latest on an amd64 host, it will pull and scan the amd64 variant of the image. If you run it on an arm64 host, it will pull and scan the arm64 variant.
This is powerful because you get security scanning tailored to the architecture you’re running on, preventing false positives or negatives that might arise from scanning an image built for a different CPU.
To explicitly scan a specific architecture from a multi-arch image, you need to target the digest of that architecture’s image. First, inspect the manifest to find the digest for the architecture you want:
docker manifest inspect myregistry/myapp:latest
This will output JSON. Look for the Manifests array. Each object in the array has an Platform field (e.g., {"architecture": "amd64", "os": "linux"}) and a Digest field. Copy the digest for the architecture you’re interested in.
Then, tell Trivy to scan that specific digest:
trivy image --platform linux/amd64 myregistry/myapp@sha256:abcdef123...
Or, if you know the digest for the ARM variant:
trivy image --platform linux/arm64 myregistry/myapp@sha256:ghijkl456...
The --platform flag tells Trivy which platform to simulate or target when fetching and analyzing the image layers, irrespective of the host it’s running on. Without it, it defaults to the platform of the host machine.
Most people assume trivy image myimage:latest will always scan all architectures defined in the manifest. That’s not the case. It intelligently picks the one matching your host’s architecture. If you need to scan a different architecture than your host, you must use the image digest and the --platform flag.
The next thing you’ll likely run into is how Trivy handles different types of vulnerabilities.