Docs
/
Docker Kubernetes
Chapter 8
08 — Docker Security
Security Best Practices
1. Run as Non-Root
# ❌ Default: runs as root
FROM node:20-alpine
COPY . /app
CMD ["node", "app.js"]
# ✅ Run as non-root user
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "app.js"]
2. Use Minimal Base Images
node:20 → ~1 GB (Debian, many packages)
node:20-slim → ~250 MB (minimal Debian)
node:20-alpine → ~180 MB (Alpine Linux, minimal)
distroless → ~30 MB (no shell, no package manager)
# Most secure: distroless (no shell to exploit)
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=build /app/dist /app
CMD ["app/server.js"]
3. Read-Only Filesystem
docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
my-app
4. No Secrets in Images
# ❌ Secret baked into image (visible in layer history)
ENV API_KEY=sk_live_abc123
# ✅ Pass at runtime
# docker run -e API_KEY=sk_live_abc123 my-app
# ✅ Use Docker secrets (Swarm) or mount secret files
# docker run -v /host/secrets:/run/secrets:ro my-app
5. Drop Capabilities
# Drop all capabilities, add only what's needed
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE my-app
# No new privileges
docker run --security-opt no-new-privileges my-app
6. Scan for Vulnerabilities
# Scan before deploying
trivy image my-app:latest
docker scout cves my-app:latest
# In CI/CD pipeline
trivy image --exit-code 1 --severity CRITICAL my-app:latest
7. Use Specific Image Tags
# ❌ Mutable tag — can change without notice
FROM node:latest
FROM node:20
# ✅ Pin to specific digest
FROM node:20.11.1-alpine
# or
FROM node@sha256:abc123...
Docker Compose Security
services:
api:
image: my-app:1.0.0
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
user: "1000:1000"
mem_limit: 512m
cpus: 1.0
pids_limit: 100
Security Checklist
□ Non-root USER in Dockerfile
□ Alpine or distroless base image
□ Specific version tags (not :latest)
□ No secrets in Dockerfile or image layers
□ .dockerignore excludes .env, .git, node_modules
□ Image scanned for CVEs
□ Read-only filesystem where possible
□ Capabilities dropped
□ Resource limits set (memory, CPU, PIDs)
□ Health checks configured
□ Signed images in production
Key Takeaways
- Never run as root — always set
USERin Dockerfile - Use alpine or distroless base images — smaller attack surface
- Never bake secrets into images — use runtime env vars or secret managers
- Scan images for vulnerabilities in CI/CD (fail on CRITICAL)
- Use
--read-only,--cap-drop ALL,--security-opt no-new-privileges - Set resource limits to prevent container from consuming all host resources