Container Image Security Checklist: 15 Steps for Production-Ready Images
Published April 21, 2026
Why Container Security Matters
Container images are the dominant deployment artifact in modern infrastructure. Every Kubernetes pod, ECS task, and Cloud Run service starts from an image. A single vulnerable or misconfigured image can compromise an entire cluster — and unlike VMs, containers often share kernel resources with other workloads on the same host.
This checklist provides 15 actionable steps to secure container images before they reach production. Each item includes the what (what to do), the why (why it matters), and the how (example commands and configurations). Bookmark this page and use it as a gate before deploying any new image.
The Checklist
Use Minimal Base Images
Start from Alpine, distroless, or scratch images instead of full-featured distributions like Ubuntu or Debian. A minimal base image contains fewer packages, which means fewer potential vulnerabilities and a smaller attack surface.
Why it matters: A standard Ubuntu 22.04 image contains 100+ packages. Alpine 3.19 contains approximately 15. Each additional package is a liability — it can contain vulnerabilities, increase image size, and provide tools an attacker could leverage post-exploitation.
# Instead of:
FROM ubuntu:22.04
# Use:
FROM alpine:3.19
# Or for Go/Rust binaries:
FROM gcr.io/distroless/static-debian12Pin Image Versions
Never use :latest tags in production Dockerfiles or Kubernetes manifests. Pin to a specific digest or version tag to ensure reproducible builds and prevent unexpected changes from upstream.
Why it matters: The :latest tag is mutable — it points to whatever the maintainer last pushed. A build that worked yesterday might pull a different image today, potentially introducing new vulnerabilities or breaking changes without any code change on your side.
# Bad: mutable tag
FROM node:latest
# Better: version tag
FROM node:20.12-alpine
# Best: SHA256 digest
FROM node@sha256:a1b2c3d4e5f6...Scan for Vulnerabilities Before Deploy
Integrate vulnerability scanning into your CI/CD pipeline. Every image should be scanned after build and before push to the registry. Block deployments that contain critical or high severity findings without an accepted risk exception.
Why it matters: Vulnerabilities caught before deployment are orders of magnitude cheaper to fix than those discovered in production. A CI gate prevents known-vulnerable images from ever reaching running environments.
# Scan with ScanRook before pushing
scanrook scan container ./my-app.tar --format json --out report.json
# In CI, fail the build on critical findings
scanrook scan container ./my-app.tar --fail-on criticalScanRook provides both CLI scanning for CI pipelines and a web dashboard for centralized visibility.
Run as Non-Root User
Always specify a non-root USER in your Dockerfile. Running as root inside a container gives an attacker immediate privilege escalation if they achieve code execution.
Why it matters: Container escape vulnerabilities are significantly more dangerous when the container process runs as UID 0. A non-root user limits the blast radius of both application vulnerabilities and container runtime exploits.
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]Use Read-Only Root Filesystem
Configure containers to use a read-only root filesystem at runtime. This prevents attackers from writing malicious files, modifying binaries, or installing additional tools after gaining access.
Why it matters: Many attack chains require writing files to disk — downloading additional payloads, creating reverse shells, or modifying configuration. A read-only filesystem blocks these post-exploitation techniques.
# Kubernetes pod spec
securityContext:
readOnlyRootFilesystem: true
# Docker run
docker run --read-only --tmpfs /tmp my-app:v1.0
# Use tmpfs mounts for directories that need writes (e.g., /tmp, /var/run)Set Resource Limits
Always define CPU and memory limits for every container. Without limits, a single compromised or misbehaving container can consume all host resources, causing denial of service for other workloads.
Why it matters: Resource exhaustion is a common attack vector. Cryptominers, fork bombs, and memory-hungry exploits can starve co-located containers. Limits ensure one container cannot monopolize host resources.
# Kubernetes
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
# Docker
docker run --memory=512m --cpus=0.5 my-app:v1.0Never Store Secrets in Images
Never bake API keys, passwords, certificates, or tokens into container images. Image layers are persistent and pushable — anyone who pulls your image can extract every layer and find embedded secrets, even if you deleted the file in a later layer.
Why it matters: Docker image layers are immutable. A secret added in layer 3 and removed in layer 4 still exists in layer 3. Registry access, image sharing, and backup systems all expose these embedded credentials.
# WRONG: Secret baked into image
COPY .env /app/.env
# RIGHT: Mount secrets at runtime
# Kubernetes: use Secrets mounted as volumes or env vars
# Docker: use --secret flag in BuildKit
docker build --secret id=db_password,src=./secret.txt .
# In Dockerfile (BuildKit):
RUN --mount=type=secret,id=db_password cat /run/secrets/db_passwordUse Multi-Stage Builds
Separate build dependencies from runtime dependencies using multi-stage Docker builds. Compilers, build tools, development headers, and test frameworks should never appear in production images.
Why it matters: Build tools expand the attack surface dramatically. A Go compiler or gcc in a production image gives attackers the ability to compile exploit code on the compromised host. Multi-stage builds ensure only the minimum runtime dependencies are present.
# Build stage: all tools available
FROM rust:1.77 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
# Runtime stage: minimal image
FROM gcr.io/distroless/cc-debian12
COPY --from=builder /app/target/release/myapp /usr/local/bin/
CMD ["myapp"]Sign and Verify Images
Use container image signing (Cosign, Notary, or Docker Content Trust) to cryptographically verify that images have not been tampered with between build and deployment. Configure your runtime to reject unsigned images.
Why it matters: Without signatures, there is no guarantee that the image running in production is the same one that passed your CI/CD security checks. Supply chain attacks can replace images in registries or intercept pulls.
# Sign with Cosign (keyless, using OIDC identity)
cosign sign --yes ghcr.io/myorg/myapp:v1.0
# Verify before deploy
cosign verify ghcr.io/myorg/myapp:v1.0 \
--certificate-identity=ci@myorg.com \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com
# Kubernetes admission control (Kyverno policy)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-images
spec:
rules:
- match:
resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["ghcr.io/myorg/*"]
attestors:
- entries:
- keyless:
subject: "ci@myorg.com"Enable SBOM Generation
Generate a Software Bill of Materials (SBOM) for every image you build. An SBOM provides a complete inventory of all packages, libraries, and dependencies inside the image — essential for incident response when new CVEs are disclosed.
Why it matters:When a critical CVE drops (like Log4Shell), you need to immediately answer: "Which of our images contain this package?" Without SBOMs, you must re-scan every image. With SBOMs, you can query your inventory in seconds. See our guide on how to read an SBOM.
# Generate SBOM with ScanRook
scanrook scan container ./my-app.tar --format json --out report.json
# The report includes full package inventory (SBOM)
# Generate with Syft
syft my-app:v1.0 -o cyclonedx-json > sbom.json
# Attach SBOM to image (Cosign)
cosign attach sbom --sbom sbom.json ghcr.io/myorg/myapp:v1.0Scan for License Compliance
Check that all packages in your images comply with your organization's license policy. Copyleft licenses (GPL, AGPL) in commercial products can create legal obligations. Scan for license issues alongside vulnerabilities.
Why it matters: License violations can result in forced open-sourcing of proprietary code, financial penalties, or injunctions. Catching a problematic license dependency before shipping is far less costly than discovering it after a compliance audit. See our license compliance guide.
# ScanRook includes license detection in scan results
scanrook license ./my-app.tar --format json
# Review license findings in the ScanRook dashboard
# Filter by license family: GPL, AGPL, SSPL, etc.Use Network Policies
Define Kubernetes NetworkPolicies (or equivalent) to restrict which containers can communicate with each other. By default, all pods in a Kubernetes cluster can talk to all other pods — this flat network is a lateral movement paradise for attackers.
Why it matters: If an attacker compromises one container, network policies limit what they can reach. A web frontend should not be able to connect directly to a database. Network segmentation is defense in depth for container environments.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-only-from-frontend
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080Set Up Scheduled Re-Scans
Images that were clean at build time can become vulnerable as new CVEs are disclosed. Schedule periodic re-scans of all images running in production to detect newly published vulnerabilities affecting existing deployments.
Why it matters: The average time between a CVE being published and an exploit being available is shrinking. An image built and scanned a month ago might now contain a critical vulnerability that did not exist at build time. Continuous scanning closes this detection gap.
# ScanRook registry integration: automatic scheduled scans
# Configure in Dashboard > Settings > Registries
# Supports: Docker Hub, GHCR, ECR, GCR, private registries
# Or schedule via cron:
0 6 * * * scanrook scan container registry.example.com/myapp:prodMonitor for New CVEs in Running Containers
Beyond scheduled scans, set up alerting when new CVEs are published that affect packages already deployed in your environment. This proactive approach means you learn about new risks within hours, not days.
Why it matters: Zero-day and critical CVE disclosures do not follow your release schedule. When a new critical vulnerability is published, you need to know immediately if your running containers are affected — without waiting for the next scheduled scan.
# ScanRook continuous monitoring:
# 1. SBOMs from all scanned images are indexed
# 2. New CVE publications are matched against indexed packages
# 3. Alerts fire when a match is found
# Webhook notification configuration:
# Dashboard > Settings > Notifications > Add Webhook
# Supports: Slack, PagerDuty, email, custom webhooksHave an Incident Response Plan
Document and rehearse your response process for when a critical vulnerability is discovered in a production image. Who gets notified? What is the rollback procedure? How quickly can you rebuild and redeploy?
Why it matters: When Log4Shell dropped in December 2021, organizations with practiced incident response plans patched within hours. Those without plans took days or weeks. The difference is preparation, not capability.
Incident response checklist:
- Identify affected images using SBOM inventory
- Assess exploitability (EPSS score, public exploit availability)
- Determine blast radius (which environments, how many instances)
- Apply mitigation (network policy, WAF rule, or rollback)
- Build patched image with updated dependency
- Scan patched image to confirm fix
- Deploy patched image through standard pipeline
- Verify fix in production and close incident
What ScanRook Handles Automatically
ScanRook automates five of the fifteen checklist items out of the box:
| Item | Checklist Step | How ScanRook Covers It |
|---|---|---|
| #3 | Vulnerability scanning | Multi-database scanning (NVD, OSV, OVAL) with CI/CD integration |
| #10 | SBOM generation | Full package inventory generated with every scan |
| #11 | License compliance | License detection and policy enforcement |
| #13 | Scheduled re-scans | Registry integration with configurable scan schedules |
| #14 | New CVE monitoring | Continuous matching of new CVEs against indexed SBOMs |
Beyond the Checklist: Defense in Depth
This checklist covers image-level security. A complete container security program also includes runtime security (syscall filtering with seccomp, AppArmor/SELinux profiles), host hardening (CIS benchmarks for the container runtime), supply chain security (SLSA provenance), and cluster-level controls (admission controllers, pod security standards).
Each layer adds defense in depth. Image hardening prevents vulnerabilities from being deployed. Runtime security detects exploitation attempts. Network policies limit lateral movement. Monitoring and alerting ensure rapid response. No single control is sufficient — the combination creates a security posture where an attacker must bypass multiple independent barriers.
For more on container scanning specifically, see our guide on container scanning best practices and our compliance scanning guide for framework-specific requirements.
Prioritizing the Checklist
If you cannot implement all 15 items immediately, prioritize in this order based on risk reduction per effort:
- Non-root user (#4) — single Dockerfile line, massive risk reduction
- Vulnerability scanning (#3) — immediate visibility into existing risk
- No secrets in images (#7) — prevents credential exposure
- Minimal base images (#1) — reduces attack surface at the foundation
- Pin versions (#2) — ensures reproducibility and auditability
- Multi-stage builds (#8) — removes build tools from production
- Resource limits (#6) — prevents denial-of-service cascades
- Read-only filesystem (#5) — blocks post-exploitation file writes
The remaining items (signing, SBOM, licensing, network policies, scheduled scans, monitoring, incident response) build upon this foundation and should be implemented as your security program matures.
Start Checking Off Your List
Upload a container image to ScanRook and immediately cover items 3, 10, 11, 13, and 14 from this checklist. Get vulnerability findings, license data, and a complete SBOM in a single scan.