Found a great image on Docker Hub. Pulled it. Deployed it. Three weeks later, security finds a cryptominer running in production.
We've all been there. Or we know someone who has. Container images are the new attack vector, and most teams don't treat them with the paranoia they deserve.
The Supply Chain Problem
You're not just deploying your code. You're deploying:
- A base image (who maintains that?)
- System packages (when were they updated?)
- Dependencies (npm, pip, cargo... how many CVEs are hiding in there?)
- Build artifacts (was the CI compromised?)
Any link in that chain can be compromised. And as the OWASP Kubernetes Security Cheat Sheet notes, securing your container images during build is critical.
Scan Everything
Image scanning tools analyze layers for known vulnerabilities. The good news: there are solid open-source options.
Trivy is my go-to. Fast, comprehensive, easy to integrate:
# Scan an image
trivy image myapp:latest
# Scan and fail on high/critical
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest
Other solid options: Clair, Grype, Snyk Container.
As Sysdig's best practices recommends: "Healthy security practices include regular scans of container images before deployment."
When to Scan
- At build time - Catch issues before they hit your registry
- In your registry - Many registries (Harbor, ECR, GCR) scan automatically
- At deploy time - Admission controllers can block vulnerable images
- Continuously - New CVEs are discovered daily; rescan regularly
Sign Your Images
Scanning tells you about known vulnerabilities. Signing tells you the image hasn't been tampered with.
Cosign (part of Sigstore) makes this straightforward:
# Sign an image
cosign sign myregistry.com/myapp:v1.0.0
# Verify before deploying
cosign verify myregistry.com/myapp:v1.0.0
Combined with admission controllers, you can enforce that only signed images run in your cluster.
Use Minimal Base Images
Every package in your image is potential attack surface. The distroless images concept is powerful: no shell, no package manager, just your app and its dependencies.
# Instead of this
FROM ubuntu:22.04
# Try this
FROM gcr.io/distroless/static-debian12
As the ARMO guide notes: "Distroless images allow you to package only your application and its dependencies in a lightweight container image, which minimizes the attack surface."
Can't go full distroless? At least use slim variants and remove unnecessary packages.
Pin Your Images
# Bad - this changes constantly
image: nginx:latest
# Better - specific version
image: nginx:1.25.3
# Best - immutable digest
image: nginx@sha256:abc123...
Using latest or mutable tags means the image you scanned might not be the one you deploy. Digests guarantee immutability.
Private Registries
Stop pulling directly from Docker Hub in production. Set up a private registry and only push vetted images:
- All images scanned before entering
- Signature verification
- Access controls
- Audit logging
Harbor is a solid open-source choice. All major cloud providers offer managed registries too.
Admission Control
Block bad images before they run. Kubernetes admission controllers can:
- Require images from trusted registries only
- Block images with critical vulnerabilities
- Enforce image signatures
- Prevent
latesttags
Tools like Kyverno or OPA Gatekeeper make this policy-driven:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: enforce
rules:
- name: check-signature
match:
resources:
kinds:
- Pod
verifyImages:
- image: "myregistry.com/*"
key: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
Don't Run as Root
Even with a good image, running as root is asking for trouble:
# Create non-root user
RUN adduser --disabled-password --gecos "" appuser
USER appuser
And in your Kubernetes manifests:
securityContext:
runAsNonRoot: true
runAsUser: 1000
The Pipeline
Put it together:
- Build with minimal base image
- Scan at build time, fail on critical vulns
- Sign the image
- Push to private registry (which scans again)
- Admission controller verifies signature and scan status
- Deploy
It sounds like a lot, but most of this can be automated in CI. The alternative—discovering a compromised image in production—is much more expensive.
Sources: