Cloud Tech by Victor
SecurityIntermediate

Container Image Scanning

How image scanning finds known vulnerabilities before a container ever runs, and why a minimal base image and a non-root user matter more for security than any scanner added afterward.

Updated 2026-07-243 min read

Overview

Image scanning builds a software bill of materials for every layer of a container image, OS packages and application dependencies alike, and checks it against a continuously updated vulnerability database, catching known-CVE versions of things the image includes before it's ever deployed. Scanning is necessary but not sufficient: Docker's own build guidance is just as much about not creating vulnerabilities to find in the first place, minimal base images with fewer packages to ever appear in a scan, multi-stage builds that drop build tools from the final image, and running as a non-root user so a successful compromise still has to escalate privilege to do real damage.

Quick Reference

PracticeWhat it doesWhy it matters
SBOM + vulnerability scanMatches every package in the image against known CVEsFinds known-vulnerable versions before deployment
Minimal base image (Alpine, distroless)Ships fewer OS packagesFewer things that can ever show up in a scan
Multi-stage buildDrops build tools/compilers from the final imageSmaller attack surface, smaller image
Non-root USERRuns the process without root privilegesA compromise still has to escalate privilege

Syntax

dockerfile
# Multi-stage build - the final image contains only the compiled
# output, not the compiler or build-time dependencies.
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN go build -o app .

FROM alpine:3.19
COPY --from=build /src/app /usr/local/bin/app
USER 1000:1000
ENTRYPOINT ["app"]

Examples

bash
# Scan an image before it's pushed to a registry, not after;
# the failing exit code is what actually gates the pipeline.
docker scout cves my-app:latest --exit-code --only-severity critical,high

A scanner finds known-vulnerable packages, not application logic bugs

Image scanning matches package versions against a CVE database; it won't catch an application's own injection vulnerability or business-logic flaw. It's one layer of a pipeline, not a substitute for SAST/DAST or secure coding practices.

Visual Diagram

Common Mistakes

  • Adding a scanner to the pipeline but only recording findings instead of failing the build on critical/high severity results.
  • Building on a full general-purpose base image out of familiarity, carrying far more scannable surface than the application actually needs at runtime.
  • Leaving the final image running as root because it was simpler during development, and never revisiting it before production.
  • Scanning once at build time and assuming the image stays safe indefinitely; new CVEs are published against already-built images constantly, so a stale, unrebuilt image drifts vulnerable over time.

Performance

  • Scanning adds real time to a build pipeline proportional to the number of packages in the image, a smaller, minimal image is also a faster image to scan.
  • Multi-stage builds can be slower to author but produce faster-to-pull, faster-to-scan, faster-to-start final images, since build-time-only dependencies never ship.

Best Practices

  • Scan every image before it's pushed to a registry, and configure the pipeline to fail on critical/high severity findings, not just report them.
  • Default to a minimal or distroless base image, and justify a larger one rather than the other way around.
  • Use multi-stage builds so compilers and build tools never end up in the image that actually runs in production.
  • Run as a non-root user via USER, and avoid sudo inside the container entirely.
  • Rebuild and rescan images on a schedule, not just once at initial build, since new vulnerabilities are discovered against already-shipped packages continuously.

Interview questions

What does a container image scanner actually check, and how does it know an image is vulnerable?

A scanner builds a software bill of materials (SBOM) for the image, an inventory of every OS package and application dependency baked into its layers, then matches that inventory against a continuously updated vulnerability database. A match means a known CVE affects a specific version of a package present in the image; the scanner doesn't analyze the application's own logic, it identifies known-vulnerable versions of things the image happens to include, which is why keeping the dependency and base-image inventory small and current matters as much as running the scanner at all.

Why does a smaller, minimal base image reduce security risk beyond just producing a smaller download?

Every package present in a base image is a package that can have a known vulnerability, and a full general-purpose distribution image bundles far more OS packages, libraries, and tools than most applications actually need at runtime. A minimal image (Alpine, a distroless image, or a multi-stage build's final stage) simply has fewer things in it that could ever show up in a vulnerability scan, which is a structural reduction in attack surface, not a mitigation that has to be maintained the way a scanner's exception list does.

Why does Docker's official build guidance recommend running as a non-root user inside a container, and why not just use sudo when root is needed?

Running as root inside a container means that a successful application-level compromise (a code-execution vulnerability, an unsafely deserialized payload) hands the attacker root inside that container immediately, with no privilege-escalation step required, and depending on the container runtime's configuration, root inside a container can sometimes be leveraged toward the host. Creating a dedicated non-root user via `USER` in the Dockerfile means a compromise still has to escalate privileges to do serious damage. `sudo` is specifically discouraged in Docker's own guidance because of its unpredictable TTY and signal-forwarding behavior inside a container, not because privilege separation itself is unnecessary.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement