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.