Container image security: minimal bases, scanning, secrets and pinning
In short: Every package in an image is something you must patch and something an attacker can use. Start from a minimal base, build in stages so tools stay out of the final image, scan each build, keep secrets out, run as a non-root user and pin by digest. Rebuild regularly so fixes actually reach production.
Why images need care
A container image is the software you actually ship. If the base image contains an old OpenSSL library, a shell and a package manager, so does your production container. Large images bring more known vulnerabilities (CVEs) to triage, more tools for an intruder to use and slower deploys. Image security is about shipping only what the application needs, and knowing exactly what that is.
Choose a minimal base and build in stages
- Minimal bases: options include Alpine, Debian or Ubuntu "slim" variants, distroless images (no shell or package manager) and scratch for static binaries. Fewer packages means fewer CVEs and fewer tools for attackers.
- Trusted sources: prefer official or verified publisher images, or an internal "golden" base maintained by your platform team.
- Multi-stage builds: compile in a full build image, then copy only the finished artefact into a small runtime image. Compilers and source code stay behind.
- Non-root user: create a user and switch to it with
USER, so a compromised process does not start as root.
# build stage
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app
# runtime stage
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
Scanning and keeping secrets out
Image scanners compare the packages in each layer against vulnerability databases and flag known CVEs. Popular open-source options include Trivy and Grype, and most registries (Amazon ECR, Azure Container Registry with Defender for Cloud, Google Artifact Registry) offer built-in scanning. Many scanners also detect secrets and misconfigurations in Dockerfiles.
trivy image --severity HIGH,CRITICAL registry.example.com/shop/api:1.4.2
trivy image --scanners secret registry.example.com/shop/api:1.4.2
Use scan results sensibly. Fail the build on critical, fixable vulnerabilities, but triage the rest using exploitability and whether the vulnerable package is actually reachable. A scanner report with 300 findings that nobody reads protects nothing.
Never bake secrets into images. Use a .dockerignore file to keep .env files, keys and the .git folder out of the build context, use build-time secret mounts (BuildKit --secret) when a build needs credentials, and supply runtime secrets as described in container secrets.
Pinning and rebuilding
The tag latest, or even 1.27, can be moved to point at new content. Pinning by digest (image@sha256:...) guarantees you deploy exactly what you tested. Tools such as Renovate or Dependabot can then propose digest updates as pull requests, so you upgrade deliberately rather than by surprise.
Pinning creates its own risk: a pinned image never picks up security fixes on its own. Schedule regular rebuilds (weekly is common) so new base layers reach production, and remove old images from the registry.
Tip: keep a short checklist in code review: minimal base, multi-stage, non-root USER, no secrets, HEALTHCHECK where useful, pinned digest, scan passed.
Practice
Why does a distroless image reduce risk after a compromise?
What is the benefit of a multi-stage build?
Your pipeline pins every image by digest. What must you add to stay secure?
Key takeaways
- Ship only what the application needs: minimal bases and multi-stage builds.
- Run as a non-root user by default.
- Scan every build for CVEs and secrets, and triage results sensibly.
- Keep secrets out of images and build context.
- Pin by digest and rebuild on a schedule to pick up fixes.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.