Why Container Image Security Matters
As container-based infrastructure has become the norm, image security is a core challenge for modern DevSecOps. Vulnerabilities that come from the base image disappear when you switch to a patched base image, so base-image management is an efficient place to start.
The attack surface falls into three main areas:
- At build time: Vulnerable base images, unnecessary packages, hardcoded secrets
- In the registry: Unsigned images, inadequate access control
- At runtime: Excessive privileges, sensitive mounts, abnormal process execution
Hardening the Build Stage
1. Use Minimal Base Images
# —
FROM ubuntu:22.04
# — distroless
FROM gcr.io/distroless/nodejs20-debian12
# or Alpine
FROM node:20-alpine3.19Alpine cuts the attack surface dramatically at around 5MB, and distroless does not even include a shell, which minimizes what an attacker can do if they compromise the container.
2. Multi-Stage Builds
#
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# — tool
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]3. Never Run as Root
# dedicated User
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuserIn Kubernetes, you can enforce this with PodSecurityContext:
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: falseVulnerability Scanning: Practical Trivy Usage
Trivy is currently the most widely used open-source container scanner.
Basic Scanning
#
trivy image nginx:1.25
# (HIGH, CRITICAL)
trivy image --severity HIGH,CRITICAL nginx:1.25
# JSON (CI )
trivy image --format json --output results.json myapp:latest
# CVE when (false positive )
trivy image --ignorefile .trivyignore myapp:latestCI/CD Integration (GitHub Actions)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_TAG }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1 # when failed
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarifGenerating an SBOM
An SBOM (Software Bill of Materials) inventories every component included in the image:
# CycloneDX SBOM
trivy image --format cyclonedx --output sbom.json myapp:latest
# SPDX
trivy image --format spdx-json --output sbom.spdx.json myapp:latestImage Signing: Cosign + Sigstore
# Cosign
cosign generate-key-pair
#
cosign sign --key cosign.key registry.example.com/myapp:latest
#
cosign verify --key cosign.pub registry.example.com/myapp:latest
# keyless (OIDC , GitHub Actions)
cosign sign --yes registry.example.com/myapp:${{ github.sha }}To allow only signed images in Kubernetes, use a Kyverno or OPA Gatekeeper policy:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-image
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["registry.example.com/*"]
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
...Runtime Security: Falco
Falco detects anomalous behavior in the container runtime in real time.
# falco-rules.yaml — example
- rule: Terminal shell in container
desc: run
condition: >
spawned_process and container and
proc.name in (shell_binaries)
output: >
Shell spawned in container (user=%user.name container=%container.name
image=%container.image.repository)
priority: WARNING
- rule: Write below etc
desc: /etc when
condition: >
open_write and container and fd.directory=/etc
output: File opened for writing below /etc (user=%user.name file=%fd.name)
priority: ERRORRegistry Security Checklist
| Item | Recommended setting |
|---|---|
| Access control | RBAC + least privilege |
| Image scanning | Enable automatic scanning on push |
| Signing policy | Allow pull of signed images only |
| Vulnerable images | Auto-quarantine on CRITICAL findings |
| Audit logging | Record all pull/push events |
| Retention policy | Automatically delete old images |
Hardening Roadmap
- Apply immediately (1 week): Integrate Trivy into the CI pipeline; fail builds on CRITICAL vulnerabilities
- Short term (1 month): Multi-stage builds + migrate to distroless/Alpine; enforce non-root users
- Medium term (3 months): Introduce Cosign image signing; apply Kyverno signature-verification policies
- Long term (6 months): Falco runtime detection; complete automated SBOM generation and vulnerability tracking
Image security is not a one-time setup. New CVEs are published every day, so the key is building automation that periodically rescans deployed images and keeps base images up to date.
Vulnerability Scan Results Decision Table
When Trivy dumps hundreds of findings, trying to fix everything and ending up fixing nothing is the worst outcome.
| Situation | Assessment | Action |
|---|---|---|
| Critical + a fix is available | Respond immediately | Bump the base image/package version → rebuild and redeploy |
| Critical but no patch released | Assess exposure | Check whether the library is actually on the execution path — if unused, register an exception (expiration date required); if in use, document mitigations (e.g., network isolation) |
| Most vulnerabilities originate from the base image | Improve the structure | Replacing with a minimal image (distroless/Alpine) is more effective than patching individually |
| The same CVE reappears every week | Process problem | No periodic base-image rebuild pipeline (weekly) — automate it |
| Judged a false positive | Record the rationale | Register it in .trivyignore with comments for reason, reviewer, and expiration date |
Operations Checklist
- Document CI gate criteria — e.g., Critical = block, High = warn then remediate within 7 days
- Expiration dates on the exception (ignore) list — no permanent exceptions
- Verify that runtime image tags match scanned image tags (scanning
latestis meaningless) - Enforce signature verification at admission control — otherwise signing is meaningless
Frequently Asked Questions (FAQ)
Q. How do you verify container image security? A. (1) Vulnerability scanning — inspect OS and library CVEs with Trivy or Grype. (2) Image signature verification — sign and verify with Cosign to prevent tampering. (3) SBOM generation — inventory components. (4) Minimize the base image (distroless/slim) to shrink the attack surface. The key is gating scans in the CI pipeline so that a failed scan blocks deployment.
Q. When should you scan images? A. At all three stages: at build time (CI), after registry registration, and at runtime (periodic rescan). CVEs continue to be disclosed after deployment, so images that are already running must be revalidated periodically.
Sources and checks vary by article. Only articles with a recorded check date, scope and sources below were checked, and only within that scope. For other articles, verify against the current docs for your version before applying anything, and email us about errors. Editorial policy · email us
This article has no recorded human check (date and scope) yet.
Comments
Be the first to comment.