Container security is more than running a scanner against your images. It’s a set of practices across the build, deploy, and runtime phases. Here’s what matters.

Build Phase

Use Minimal Base Images

Every package in your image is attack surface. Start minimal:

# Bad: 1GB+ image with compilers, shells, and tools
FROM node:20

# Better: ~150MB slim image
FROM node:20-slim

# Best for compiled languages: ~5MB scratch image
FROM gcr.io/distroless/static-debian12

Distroless images remove shells, package managers, and other tools that attackers rely on. If an attacker gets code execution in a distroless container, there’s not much they can do with it.

Multi-Stage Builds

Separate build dependencies from runtime:

FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=build /app/dist /app
WORKDIR /app
CMD ["server.js"]

Your build tools, source code, and dev dependencies never make it into the production image.

Pin Versions

Floating tags are a supply chain risk:

# Don't do this
FROM node:latest

# Do this
FROM node:20.12.2-slim@sha256:abc123...

Pin both the tag and the digest. The tag gives you readability; the digest gives you immutability.

Runtime Phase

Run as Non-Root

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

If your process doesn’t need root (and it almost certainly doesn’t), don’t run as root.

Read-Only Filesystem

Mount the container filesystem as read-only and explicitly whitelist writable paths:

securityContext:
  readOnlyRootFilesystem: true
volumes:
  - name: tmp
    emptyDir: {}

Drop Capabilities

Linux capabilities are granular root privileges. Drop them all and add back only what you need:

securityContext:
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]

Supply Chain

  • Sign your images. Use cosign or Notary to create verifiable signatures.
  • Scan continuously. Not just at build time — new CVEs are published daily against existing packages.
  • Use a private registry. Control what enters your environment.
  • Enforce admission policies. Use a policy engine to reject unsigned or vulnerable images at deploy time.

Security isn’t a checklist you complete once. It’s a continuous process built into your pipeline.