| name | apply-docker-security |
| description | Use when writing Dockerfiles, configuring container runtimes, or deploying containerized workloads — to harden containers against privilege escalation, image vulnerabilities, and container escape. |
| source | OWASP Docker Security Cheat Sheet (owasp.org/www-project-cheat-sheets); CIS Docker Benchmark; NIST SP 800-190 (Application Container Security); CWE-250 |
| tags | ["security","owasp","docker","containers","devops","developer"] |
Apply Docker Security
Harden Docker containers by running as non-root, using read-only filesystems, scanning images for vulnerabilities, and applying seccomp/AppArmor profiles — preventing privilege escalation and container escape.
Why This Is Best Practice
Adopted by: OWASP Docker Security Cheat Sheet and CIS Docker Benchmark (Center for Internet Security) are the two authoritative references. NIST SP 800-190 (Application Container Security Guide, 2017) provides federal guidance. Google Cloud, AWS ECS, and Azure Container Apps all enforce non-root and read-only filesystem policies in their hardened runtime configurations. Kubernetes Pod Security Standards (baseline/restricted) mandate non-root and non-privileged containers.
Impact: Container escape vulnerabilities (CVE-2019-5736 runc, CVE-2020-15257 containerd) allow processes inside containers to gain host-level access — most exploitable when the container runs as root. The 2021 Codecov supply chain attack used compromised Docker images in CI pipelines to steal credentials from thousands of organizations. Trivy and Snyk Container scans find high/critical CVEs in 60%+ of production images according to Snyk's State of Open Source Security 2023.
Why best: Running containers with the --privileged flag or as root with volume mounts to / is equivalent to running on the host with sudo. Rootless containers, read-only filesystems, and capability dropping eliminate entire classes of container escape and privilege escalation attacks with minimal operational overhead.
Sources: OWASP Docker Security Cheat Sheet; CIS Docker Benchmark v1.6; NIST SP 800-190; CWE-250
Steps
-
Run as a non-root user — never use USER root in production images:
FROM node:20-alpine
# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --production
# Drop to non-root before CMD
USER appuser
CMD ["node", "server.js"]
For existing images that default to root: override with --user 1000:1000 at runtime.
-
Use minimal base images — reduce attack surface by minimizing installed packages:
# Alpine: ~5MB, minimal attack surface
FROM python:3.12-alpine
# Distroless: no shell, no package manager, minimal CVE surface
FROM gcr.io/distroless/python3:nonroot
# Multi-stage: build dependencies stay in builder, only runtime artifacts in final
FROM python:3.12-alpine AS builder
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt
FROM gcr.io/distroless/python3:nonroot
COPY --from=builder /install /usr/local
COPY app.py .
CMD ["app.py"]
-
Make the filesystem read-only — prevent runtime file modification:
docker run --read-only --tmpfs /tmp --tmpfs /run myapp
services:
app:
read_only: true
tmpfs:
- /tmp
- /var/run
securityContext:
readOnlyRootFilesystem: true
-
Drop all capabilities and add only what's needed:
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
Most apps need zero Linux capabilities — run on port ≥1024 to avoid even NET_BIND_SERVICE.
-
Scan images for vulnerabilities in CI:
Rules
- Never use
--privileged in production — it gives the container all Linux capabilities and disables seccomp/AppArmor.
- Never mount the Docker socket (
/var/run/docker.sock) inside a container — it gives full host access.
- Pin base image tags to digests in production:
FROM node:20-alpine@sha256:... prevents supply chain attacks via tag mutation.
- Set
no-new-privileges: true to prevent setuid binaries from escalating inside the container.
Common Mistakes
FROM ubuntu:latest as base image — includes hundreds of unnecessary packages, each a potential CVE.
- Storing secrets in
.env files copied into the image — visible in docker history and image layers.
- Not setting resource limits — a single runaway container can starve all others on the host.
- Running as root because "it's just a container" — container boundaries are not security boundaries without hardening.