Why can Trivy 0.74's `trivy image` scan a different copy of the same tag on a laptop than in CI, and which flags pin that choice?
answer
- where the image is found
- first source wins
- docker, containerd, podman, remote
- architecture of a multi-arch index
basics
~20 strivy image scans the first copy it finds, trying Docker Engine, containerd, Podman and then the registry, so a stale local pull or another architecture can be scanned. --image-src fixes the source order and --platform chooses the architecture.
solid answer
~40 sTrivy 0.74 looks for an image in `--image-src` order, default `docker,containerd,podman,remote`, and scans the first source that has it; a source whose daemon is not running is skipped. On a laptop with Docker Engine running, an old local pull of `api:1.4` wins even if the tag was re-pushed; a CI runner without a daemon falls through to the registry. Architecture adds a second split: without `--platform`, a registry pull of a multi-arch index loads `linux/amd64`, while a laptop daemon may hold another variant. To make the scan reproducible, use `--image-src remote`, reference the image by digest, and pass `--platform linux/arm64` (or whichever you ship). In 0.74's code the Docker Engine source does not apply `--platform`, which is another reason to pin the source.
code
bash · 10 lines# laptop: may scan a stale local copy from Docker Engine
trivy image registry.example.com/shop/api:1.4
# reproducible: registry copy, chosen architecture
trivy image --image-src remote --platform linux/arm64 \
registry.example.com/shop/api:1.4
# not pushed yet: scan the exported tarball
docker save registry.example.com/shop/api:1.4 -o api-1.4.tar
trivy image --input api-1.4.targo deeper
Know that trivy image looks locally before the registry, so a laptop may scan an old pull of the tag.
Explain the default order docker, containerd, podman, remote, what --image-src changes, and how --platform selects a manifest from a multi-arch index.
Make gate scans reproducible: digest references, --image-src remote and an explicit --platform, and recognise a stale local copy when two reports disagree.
Decide which artefact identity the organisation's scan evidence refers to, so that a report always maps to exactly the bytes that were deployed.
## The search order `trivy image` takes an image **name**, not a location. Trivy 0.74 resolves the name against a list of **image sources** and scans the first copy it finds: 1. **`docker`**: the local Docker Engine, skipped if no engine is running. 2. **`containerd`**: the local containerd, skipped if it is not running. 3. **`podman`**: local Podman 2.0 or later through its socket; a remote Podman is not supported. 4. **`remote`**: the container registry named in the reference. The list is the `--image-src` flag (`image.source` in `trivy.yaml`), default `docker,containerd,podman,remote`. Passing it **replaces** the order: `--image-src podman,containerd` searches Podman, then containerd, and fails if neither has the image, without falling back to the registry. ## Why a laptop and CI disagree - **A stale local copy.** A developer pulled `registry.example.com/shop/api:1.4` last month; the tag has since been rebuilt and re-pushed. The laptop's Docker Engine still has the old copy, and because `docker` is first in the default order, that is what gets scanned. - **No daemon in CI.** A typical CI container has no engine, so the same command falls through to `remote` and scans the current registry copy. - **A different architecture.** For a multi-arch index pulled from a registry, Trivy loads `linux/amd64` unless told otherwise. A laptop on another CPU architecture usually holds its native variant locally, so the package builds and even the versions can differ. ## Pinning the source | Source | How to point Trivy at it | |---|---| | Docker Engine | `--docker-host` or `DOCKER_HOST` | | containerd | `CONTAINERD_ADDRESS` (default socket `/run/containerd/containerd.sock`), `CONTAINERD_NAMESPACE` (default `default`) | | Podman | `--podman-host`, or the `podman.socket` user service | | Registry | credentials through `trivy registry login` | | A tarball or OCI layout | `--input`, which bypasses the source list | On a Kubernetes node whose images were pulled by the kubelet, containerd keeps them in the `k8s.io` namespace, so `CONTAINERD_NAMESPACE=k8s.io` is needed before Trivy can find them. ## Choosing the architecture `--platform` takes `os/arch`, for example `linux/arm64`, and selects that manifest from a multi-arch index. For a single-architecture image it is ignored (Trivy notes this at debug level). In 0.74's code the registry and containerd sources apply the platform, but the Docker Engine source does not: it scans whatever the engine stores under that name. Combine `--platform` with `--image-src remote` when the architecture matters. ## A reproducible invocation For a release gate, remove every source of ambiguity: ```bash trivy image --image-src remote --platform linux/arm64 \ registry.example.com/shop/api@sha256:<digest-from-the-build> ``` The digest fixes the content, `remote` fixes where it comes from, and `--platform` fixes which variant of an index is read. The scan cache is keyed by image ID and layer IDs, so repeating the scan of the same digest is cheap. ## Diagnosing a disagreement 1. Rerun both scans with `--debug`; Trivy logs which source the image was found in. 2. Compare the `Metadata.ImageID` and `Metadata.RepoDigests` fields of the two JSON reports: different IDs mean different images, whatever the tag says. 3. If the images differ by architecture, rerun both with the same explicit `--platform` and `--image-src remote`. 4. Only once both scans read the same image is a remaining difference worth investigating as a database or timing question. ## When the image is not in any registry An image built in a job without pushing can be exported with `docker save` and scanned with `trivy image --input api-1.4.tar`. That also helps in air-gapped review, where the scanning machine has no access to the registry at all.
- On a containerd-only Kubernetes node, why does `trivy image` not find images the kubelet pulled?Trivy reads containerd's `default` namespace unless told otherwise, and the kubelet's images live in `k8s.io`. Set `CONTAINERD_NAMESPACE=k8s.io`, and `CONTAINERD_ADDRESS` too when the socket is not at `/run/containerd/containerd.sock`, as on k3s with `/run/k3s/containerd/containerd.sock`.
- What does `--platform` do for an image that is not multi-arch?Nothing: Trivy notices the reference is a single image rather than an index, logs at debug level that it is ignoring `--platform`, and scans the one image there is.
saying these in an interview costs you the question
- trivy image always pulls the tag fresh from the registry.
- If the image is missing locally, trivy image fails.
- --image-src adds a source to the default list.
- --platform is honoured whichever source finds the image.
- Without --platform, Trivy scans every architecture in a multi-arch index.