Image Policy Best Practices
A practical, opinionated checklist for image policy and vulnerability management across build, registry, and cluster. The anchor rule: no deploy on a Critical finding without a recorded, time-boxed exception.
How to Use This List
Read each group in order; they follow the image's path from build to runtime.
Adopt the gates in the order shown, starting with CI scanning, so you get value before you enforce cluster-wide.
Treat every rule as a default you can override only with a documented reason. Bold leads are the action; the text is the why.
Remember the runtime boundary throughout: you build and scan with Docker, but containerd via the CRI runs the pods on your nodes. Policy therefore spans build, registry, and admission, not one tool.
A - Base Images
Pin every base by digest. Reference FROM image@sha256:..., not a mutable tag, so builds are reproducible and cannot shift under you.
Rebuild on a cadence. Pinning freezes CVEs in place, so schedule weekly rebuilds (or Renovate digest bumps) to pull in upstream patches.
Prefer minimal or distroless bases. Fewer packages means fewer inherited CVEs and a smaller attack surface to defend.
Keep the human-readable tag as a comment. A bare digest is unreviewable, so note the tag and date above each FROM.
Approve bases centrally. Maintain a short list of vetted, pre-scanned bases teams may build on, rather than letting every repo pick its own.
B - Building
Use multi-stage builds. Compile and install tooling in a build stage, then copy only the artifact into a clean runtime stage.
Strip shells and package managers from runtime images. Remove apt, apk, curl, and /bin/sh from the final image to delete both CVEs and attacker tooling.
Run as a non-root user. Set an explicit USER with a high UID so a container compromise does not start as root.
Do not bake in secrets. Use build secrets or runtime injection; never COPY a credential into a layer where it persists forever.
Generate and store an SBOM per build. Emit a CycloneDX or SPDX inventory so you can answer "are we affected?" without the original image.
C - Scanning & CI Gates
Scan every image in CI on every build. Run Trivy or Grype so a vulnerable image fails fast before it reaches a registry.
Block the build on Critical fixable findings. Use --exit-code 1 --severity CRITICAL --ignore-unfixed as the baseline gate, then tighten to High.
Run two scanners where practical. Trivy and Grype use different feeds and matchers, so a cross-check reduces blind spots.
Publish scan results durably. Upload SARIF to code scanning so findings are tracked over time, not just printed and lost.
Re-scan images in the registry on a schedule. New CVEs land daily, so registry re-scans catch images that went bad after they were pushed.
D - Deployment & Admission
Require digests at admission. A ValidatingAdmissionPolicy that rejects tags without @sha256: forces reproducible references cluster-wide.
Require signatures. Enforce cosign/Sigstore verification so only images your pipeline signed can run.
No deploy on Critical without a recorded exception. This is the anchor rule: a Critical finding blocks the deploy unless a time-boxed, owned waiver exists.
Apply the restricted Pod Security Standard. Enforce restricted so non-root, dropped capabilities, and no privilege escalation are cluster defaults.
Set a read-only root filesystem and drop all capabilities. Harden the securityContext so the runtime enforces what a minimal image implies.
E - Exceptions & Governance
Time-box every waiver. Set an expires date so an exception cannot silently become permanent policy.
Name an owner on every exception. Accountability is the point; route it to a team or service owner, not a departing individual.
Scope waivers to one CVE and one service. Narrow scope keeps new instances of the same CVE elsewhere still caught.
Require a separate approver. No one waives their own risk; a security owner or lead reviews each exception.
Track mean time to remediate. Measure how fast Critical findings get fixed as a platform SLA, and report expiring waivers weekly.
When You Are Done
You should have digest-pinned, minimal bases that rebuild on a cadence and pass a CI scan gate on every build.
You should have admission control requiring digests and signatures, with the restricted Pod Security Standard enforced cluster-wide.
And you should have a waiver system where every exception is scoped, owned, time-boxed, and reviewed - so "no Critical without a recorded exception" holds in practice.
Revisit the list each quarter. New CVE classes, base-image options, and admission features appear constantly, and a policy that never changes slowly drifts out of date.
Finally, measure the outcome, not just the controls. If mean time to remediate is falling and Critical findings rarely reach production, the policy is working.
FAQs
What severity should block a deploy? Start by blocking Critical fixable findings, then extend to High once the backlog is under control.
How do we ship when a Critical CVE has no fix? Through a recorded, time-boxed exception with a named owner and, ideally, a compensating control.
Where should the first gate live? In CI on every build, so vulnerable images never reach a registry or cluster.
Do registry scans replace CI scans? No. CI blocks bad images from publishing; registry scans catch images that became vulnerable after publishing.
Is Docker the in-cluster runtime these policies protect? No. You build and scan with Docker, but containerd via the CRI runs the pods on nodes.
Related
- The Container Vulnerability Lifecycle
- Vulnerability Basics
- Base Image Pinning by Digest
- Exception Process
- Minimal Attack Surface
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).