Reduce attack surface of containers and avoid common pitfalls (root user, copying secrets, exposed ports).
container security is leaving your house: don’t leave windows open, keys on the table, alarm off.
Risks and mitigations:
1. running as root -> USER 1000
2. secrets in layers -> BuildKit secrets / runtime env
3. vulnerable base images -> distroless / alpine; Trivy scan
4. privileged mode + mounts -> CAP_DROP ALL; --read-only fs
5. publishing too many ports -> publish only what you need
(1) Run as a non-root user (USER 1000). (2) Use minimal base images (distroless, alpine, scratch for static binaries). (3) Drop Linux capabilities (--cap-drop=ALL then add back only what’s needed). (4) Make filesystem read-only (--read-only). (5) Limit resources (--memory, --cpus). (6) Scan images regularly (Trivy, Clair, Snyk). (7) Multi-stage builds. (8) Sign images (Docker Content Trust / sigstore).
FROM python:3.12-slim
RUN useradd --create-home --uid 1000 app
WORKDIR /home/app
COPY --chown=app:app . .
USER app
CMD ["python", "app.py"]
creates non-root user, copies code with correct ownership, switches to it.
docker run --cap-drop=ALL --read-only \
--tmpfs /tmp --tmpfs /run \
--memory 256m --cpus 0.5 myapp:1.0.0
drops all Linux capabilities, makes root fs read-only; mounts writable tmpfs.
trivy image --severity HIGH,CRITICAL myapp:1.0.0
# fail CI based on findings count
scans the image for known CVEs; integrate into CI to fail builds with critical CVEs.
Running as root. Adding secrets to ENV (visible in docker inspect). Using --privileged (huge attack surface). Leaving SSH server in the image.
non-root, minimal base, read-only FS, dropped caps, scanned regularly, signed. NEVER bake secrets into layers.
--privileged.--privileged in production; OPA / admission controller rejects it.docker inspect revealed the prod DATABASE_URL including password; logs included the URL.ENV DATABASE_URL=postgres://user:prod123@db/app in the Dockerfile.