A container image audit is a systematic inspection of everything baked into an image — the base operating system packages, your application's dependencies, embedded secrets, and runtime configuration — so you can find and fix exploitable weaknesses before the image ever runs in production. Images are immutable and often long-lived, which means a vulnerability shipped in one propagates to every container started from it. A disciplined container image security audit, wired into your build pipeline, is how you stop that propagation. This guide walks through what to audit, in what order, and how to make it repeatable.
Why images are their own risk surface
A container image is not just your code. It is a stack of layers: a base image (often a full or slim Linux distribution), system libraries, a language runtime, your dependencies, and finally your application. Each layer contributes packages you may never have chosen directly. An outdated openssl in the base or a vulnerable transitive npm package deep in your app layer are equally your problem once the image runs.
The audit exists because you inherit all of it. Pulling python:3.12 gives you hundreds of OS packages you did not pick. The question a container image audit answers is: of everything in this image, what is known-vulnerable, what should not be here, and what is misconfigured?
Start with the base image
The base image is the largest single source of packages, so it is the first thing to audit. Two questions matter. First, is it current? A base pinned to a tag that has not been rebuilt in months carries whatever CVEs have been disclosed since. Second, is it as small as it needs to be? A -slim or distroless base has fewer packages, which means fewer things to patch and a smaller attack surface.
# Larger surface: full distro, many packages you don't use
FROM node:20
# Smaller surface: fewer OS packages to audit and patch
FROM node:20-slim
Prefer minimal bases, pin them by digest for reproducibility, and rebuild regularly so base fixes flow into your images. An image that is never rebuilt slowly rots as new vulnerabilities are published against its frozen packages.
Scan OS packages and application dependencies
The core of the audit is scanning both layers against vulnerability databases. Open-source scanners like Trivy and Grype read the image, enumerate OS packages and language dependencies, and match them against known CVEs:
# Scan an image for OS + language vulnerabilities
trivy image myorg/api:1.4.2
# Fail the build on high/critical findings
trivy image --severity HIGH,CRITICAL --exit-code 1 myorg/api:1.4.2
The application dependencies are where software composition analysis matters most, because a single deep transitive package can carry a critical CVE that no base-image update will fix. A good audit treats OS packages and app dependencies as one combined inventory rather than two separate concerns — an attacker does not care which layer the vulnerable library lives in.
Hunt for secrets and sensitive files
Vulnerabilities are only half the audit. Images routinely ship with things that should never be baked in: a cloud credential copied during a build step, a private key, a .env file, an .aws/credentials left in a copied directory. Because layers are additive, a secret added in one layer and "removed" in a later one still exists in the earlier layer and is fully recoverable.
# Detect secrets committed into image layers
trivy image --scanners secret myorg/api:1.4.2
Audit your Dockerfile for COPY . . patterns that sweep in more than intended, and use a .dockerignore to keep credentials, .git, and local config out of the build context entirely. A leaked long-lived credential in a published image is often a worse incident than a package CVE.
Check configuration and provenance
Beyond content, audit how the image is configured to run. Does it run as root, or does the Dockerfile set a non-root USER? Are there unnecessary setuid binaries? Is the entrypoint dropping privileges? These configuration choices decide how much an attacker gains if they do exploit something inside.
Increasingly, provenance is part of the audit too. Is the image signed? Can you verify who built it and from what source, and does it come with an attested SBOM? For AI and ML workloads this has sharpened: images that bundle model weights or pull models at build time inherit the risk of those artifacts, so auditing what an image fetches and from where is now a first-class concern, not an afterthought.
Make it a gate, not a one-off
A container image audit you run manually once is worth little, because the next build reintroduces whatever you fixed. Wire it into CI so every image is scanned on build and the pipeline fails on policy violations — a critical CVE with a fix available, a detected secret, a disallowed license, or a root-running config. Generate and store an SBOM per image so you can answer "which of our running images contain package X?" the day a new CVE lands, without re-scanning everything from scratch. Platforms such as Safeguard can attach these findings to the specific project and image so remediation has an owner. Continuous auditing across the registry — not just at build — catches images that were clean when built but have since had CVEs disclosed against their frozen contents.
FAQ
What does a container image audit check for?
Four things: known vulnerabilities in the base OS packages, known vulnerabilities in your application dependencies, secrets or sensitive files baked into layers, and insecure runtime configuration such as running as root. A thorough audit also verifies image provenance and generates an SBOM.
What tools run a container image security audit?
Open-source scanners like Trivy and Grype enumerate OS and language packages and match them against CVE databases, and can also detect secrets. Pair image scanning with software composition analysis for deep transitive dependencies, and run everything in CI so results gate the build.
Why do old container images become insecure even if nothing changed?
Images are immutable, so their package versions freeze at build time, but new vulnerabilities are disclosed against those versions continuously. An image that was clean months ago can accumulate critical CVEs. Rebuild from updated bases regularly and re-scan images already in your registry.
How do I stop secrets from ending up in an image?
Use a .dockerignore to exclude credentials, .git, and local config from the build context, avoid broad COPY . . steps, and never bake long-lived secrets into layers — inject them at runtime instead. Scan images for secrets in CI, since a secret in an early layer survives even if a later layer deletes it.