Docker Security
Best practices for secure containers — non-root, minimal base, scanning, signing.
ContainersAdvanced
Why
Container security reduces attack surface: non-root user, minimal base image, vulnerability scanning, and image signing (cosign) prevent tampering and exploits. Docker is used by 59% of professional developers per the Stack Overflow 2024 survey, and Kubernetes runs 60% of containerized workloads in production (CNCF 2024 survey).
Container orchestration reduces deployment-related incidents by 35% through declarative rollouts.
When
Apply to all production containers — security is not optional. Skip containers for simple static sites — a CDN + static export is cheaper and faster.
Avoid Kubernetes for teams under 10 people; the operational overhead is not justified.
How
1. Non-root — USER 1000 in Dockerfile
2. Minimal — distroless or alpine base
3. Scan — trivy, grype, or docker scout
4. Sign — cosign for Sigstore signing
5. Read-only — read_only: true in compose
6. No capabilities — cap_drop: [ALL]
7. Secrets — Docker secrets or external
Example
# Dockerfile — secure
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --chown=app:app . .
USER app # non-root
CMD ["node", "server.js"]
# docker-compose.yml — hardening
services:
app:
image: myapp:latest
user: "1000:1000"
read_only: true
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
tmpfs: [/tmp]
# Scan
trivy image myapp:latest
# Sign with cosign
cosign sign --key cosign.key myapp:latest
cosign verify --key cosign.pub myapp:latest
Common Problems
- ⚠Container runs as root — default, add USER directive for non-root
- ⚠Vulnerable base image — scan regularly, update base on CVEs
- ⚠Configuration drift between environments — use infrastructure-as-code (Terraform/Pulumi) to keep dev/staging/prod identical
Best Practices
- ✓Run as non-root user — USER 1000, eliminates most privilege escalation
- ✓Use distroless base — no shell, no package manager, minimal attack surface
- ✓Set up health checks and graceful shutdown — handle SIGTERM to drain in-flight requests before exit