Container and Image Security

By Davy Rogers

Your container is only as secure as what's inside it. Most have way too much.

Container = image + config + runtime. Every component is a vuln surface.

Base images

BaseSizeUse
ubuntu:24.04~77 MBDev
node:22-slim~180 MBNode
gcr.io/distroless/nodejs22~130 MBProd
alpine:3.20~7 MBMinimal
scratch0 MBStatic Go

Distroless: No shell, no package manager. Attacker gets code execution - can't easily explore.

Multi-stage builds

FROM node:22-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build

FROM gcr.io/distroless/nodejs22
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["dist/server.js"]

Final: app + prod deps. No build tools, source.

Non-root

RUN addgroup --system app && adduser --system --ingroup app app
USER app

Pin versions

# GOOD
FROM node:22.5.1-slim@sha256:abc123...

No secrets in layers

# BAD
COPY .env /app/.env  # Persists in layer history

# GOOD - BuildKit secrets
RUN --mount=type=secret,id=db_password \
    DB_PASSWORD=$(cat /run/secrets/db_password) npm run setup

Scanning

trivy image myapp:latest
grype myapp:latest

CI: fail on CRITICAL/HIGH. But scanners only find known CVEs - not your code vulns or misconfigs.

Signing

cosign sign --key cosign.key myregistry.com/myapp:latest
cosign verify --key cosign.pub myregistry.com/myapp:latest

Runtime

Read-only filesystem: read_only: true

Drop capabilities: capabilities: drop: ["ALL"]

Network policies: Restrict pod communication.

Resource limits: Prevent DoS.

The takeaway

Minimal images. Multi-stage. Non-root. Pin digests. No secrets in layers. Scan in CI. Sign and verify.

Runtime: read-only filesystem, drop capabilities, network policies, resource limits.

Every layer you remove is one less thing to patch, scan, and defend.

Want a professional to look at it?Get an AppSec Health Check.