Container hardening is least privilege applied to runtime boundaries.
The goal is not to make compromise impossible. The goal is to prevent one compromised container from controlling the host, runtime, cluster, or neighboring workloads.
Docker Runtime Hardening
Avoid privileged mode. There is no clean hardening switch that makes --privileged safe for normal application workloads; remove it first.
Use no-new-privileges:
docker run --security-opt no-new-privileges:true IMAGE
Drop capabilities:
docker run --cap-drop=ALL IMAGE
Add only what is required:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE IMAGE
Run as non-root:
docker run --user 1000:1000 IMAGE
Read-only root filesystem:
docker run --read-only IMAGE
Limit resources:
docker run --memory 256m --cpus 1 --pids-limit 100 IMAGE
Mount Hardening
Avoid:
/:/host
/var/run/docker.sock:/var/run/docker.sock
/etc:/host/etc
/root:/host/root
/var/lib/docker:/var/lib/docker
Prefer:
narrow application-specific paths
read-only mounts
nosuid
nodev
noexec
Example:
docker run -v /srv/app/config:/app/config:ro IMAGE
Docker Socket
Do not mount:
/var/run/docker.sock
If containerized build or automation needs Docker access:
- Use a dedicated build host.
- Use rootless builders where possible.
- Use a restricted Docker socket proxy.
- Restrict which actions the automation can perform.
- Separate build workloads from production workloads.
Also treat the local Docker group as privileged. A user in the docker group can usually create containers that mount the host filesystem, which makes group membership close to root-equivalent on the Docker host.
API Exposure
Check daemon listeners:
ss -lntp | grep dockerd
ps aux | grep dockerd
Do not expose unauthenticated Docker API on TCP.
If TCP is required:
- Use mutual TLS.
- Firewall trusted clients only.
- Monitor requests.
- Rotate client certificates.
Review daemon configuration:
cat /etc/docker/daemon.json 2>/dev/null
ps aux | grep dockerd
Dangerous listener patterns include:
tcp://0.0.0.0:2375
tcp://*:2375
Security Profiles
Keep these enabled:
seccomp
AppArmor
SELinux
no-new-privileges
Do not disable profiles casually:
--security-opt seccomp=unconfined
--security-opt apparmor=unconfined
--privileged
These options are strong signals for review.
Where practical, enable user namespace remapping or rootless Docker so container UID 0 does not map directly to host UID 0.
Secrets
Avoid secrets in:
environment variables
image layers
command-line arguments
world-readable mounted files
logs
Prefer:
- Runtime secret mounts
- Short-lived credentials
- Narrow service accounts
- Secret rotation
- Separate secrets per workload
Monitoring
Watch for:
- New privileged containers
- Docker socket mounts
- Host root bind mounts
- Host PID/network mode
- Dangerous capabilities
- Disabled seccomp/AppArmor
- Containers running as root unexpectedly
- Unusual Docker API requests
- Unexpected container images
- Containers mounting sensitive host paths
Useful commands:
docker events
docker ps --no-trunc
docker inspect CONTAINER
Checklist
- No Docker socket in app containers
- No unauthenticated Docker API
- No privileged containers unless formally approved
- Capabilities dropped by default
- Containers run as non-root
- Root filesystem read-only where possible
- Host mounts narrow and read-only where possible
- Seccomp and AppArmor/SELinux enabled
- Secrets not stored in images or env vars
- Kernel and runtime patched
- Container creation events monitored