Containerization without security hardening creates widespread host escalation vulnerabilities. Enforcing the Center for Internet Security (CIS) Docker Benchmark requires rootless daemon execution, read-only root filesystems, Linux capability dropping, and strict AppArmor/Seccomp syscall confinement.
What Are the Core Tenets of the CIS Docker Benchmark?
Docker revolutionized software engineering by packaging applications and their dependencies into portable container images. However, a dangerous misconception persists among infrastructure engineers: that containers represent secure security boundaries comparable to virtual machines. In reality, containers are not virtual machines; they share the host Linux kernel directly, relying on kernel namespaces and cgroups for isolation.
If an application running inside a standard Docker container is compromised, and that container executes as the default root user (UID 0), an attacker who discovers a kernel vulnerability or a misconfigured volume mount can execute a container breakout, achieving full root compromise over the underlying physical host. Hardening container runtimes against the CIS Docker Benchmark is mandatory for enterprise infrastructure.
| Security Control | Default Docker Deployment | Hardened CIS Benchmark Standard | Exploit Mitigation Impact |
|---|---|---|---|
| Container User Context | Root (UID 0 inside and outside) | Rootless Mode / User Namespaces | Eliminates host root escalation even if container is broken. |
| Root Filesystem Access | Read-Write (–read-only disabled) | Strict Read-Only (–read-only=true) | Prevents malware from dropping backdoors into system binaries. |
| Linux Capabilities | 14 default root capabilities | Drop ALL (–cap-drop=ALL) | Strips CAP_NET_RAW, CAP_SYS_ADMIN, and raw socket access. |
| Privilege Escalation Flag | Allowed (setuid binaries work) | no-new-privileges:true | Blocks SUID/SGID privilege escalation within the container. |
| Syscall Confinement | Default loose seccomp | Custom Seccomp & AppArmor Profiles | Blocks dangerous kernel calls (e.g., ptrace, keyctl, unshare). |
Rootless Docker vs User Namespaces (userns-remap)
The foundational remediation in container defense is eradicating UID 0 execution. In default Docker installations, the Docker daemon runs as host root, and processes inside containers map directly to UID 0 on the host process table. If an application escapes its container namespace, it is already root on the physical server.
Two distinct architectures solve this risk: Rootless Docker runs the entire Docker daemon and container runtime inside an unprivileged user namespace without root privileges. If the daemon itself is compromised, the attacker only acquires unprivileged user access.
Alternatively, in environments requiring systemd integration, enabling User Namespace Remapping (userns-remap) in /etc/docker/daemon.json maps container UID 0 to an unprivileged subordinate user ID on the host (such as UID 100000). To the containerized process, it appears to be root; to the host Linux kernel, it possesses zero administrative privileges, neutralizing file write access to host /etc or /boot.
For SecOps engineers hardening container fleets, cross-referencing our hardware and Linux guides—such as Hardening Docker & Proxmox Containers and Linux Kernel Hardening via Sysctl & eBPF—provides ready-to-deploy configuration scripts.
Dropping Linux Capabilities and Read-Only Roots
In traditional Linux, permissions were binary: root could execute all 38 kernel capabilities, while unprivileged users had none. Modern Linux divides superuser privileges into granular Capabilities (e.g., CAP_NET_BIND_SERVICE, CAP_SYS_ADMIN, CAP_NET_RAW).
Most containerized web applications only require reading files and binding to unprivileged network ports. They have no operational need to alter network routing tables (CAP_NET_ADMIN) or construct raw network packets (CAP_NET_RAW used in ARP spoofing). Hardened Docker configurations enforce a strict least-privilege capability stance:
services:
web-api:
image: my-app:latest
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
Enforcing read_only: true locks down the entire container root filesystem into an immutable image. An attacker cannot download webshells, alter binaries, or modify system libraries; any temporary file writes are strictly quarantined to a memory-backed, non-executable tmpfs partition.
Architectural Takeaways:
- Eradicate Host Root: Deploy user namespace remapping to ensure container UID 0 has zero host privileges.
- Immutable Filesystem: Mount containers with –read-only and utilize noexec tmpfs for temporary caching.
- Capability Discipline: Always drop ALL capabilities by default and explicitly add back only required tokens.
Custom Seccomp BPF Filtering to Restrict System Calls
While Linux capabilities restrict administrative actions such as binding to privileged network ports or mounting filesystems, an application running inside a container can still execute hundreds of distinct system calls directly against the host Linux kernel. If an attacker exploits an application vulnerability to execute arbitrary shellcode, they can invoke obscure or legacy kernel syscalls to trigger local privilege escalation.
Docker applies a default seccomp profile that blocks approximately 44 system calls, including reboot, swapon, and ptrace. However, high-security production environments implementing CIS benchmarks should enforce customized, restrictive seccomp whitelists tailored to the specific workload.
To inspect and audit system calls made by your container in development, run strace or auditd, then craft a targeted seccomp JSON policy profile that sets defaultAction: "SCMP_ACT_ERRNO" and whitelists only the explicit syscalls required for operation. Deploy the container with:
docker run -d \
--name secured-app \
--security-opt seccomp=/etc/docker/seccomp/app-whitelist.json \
--security-opt no-new-privileges:true \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
custom-service:v2
Combining custom seccomp profiles with no-new-privileges:true ensures that child processes executed via execve cannot acquire elevated permissions via setuid binaries, neutralizing the most common local escalation vectors.
Daemon Configuration Hardening via daemon.json
/etc/docker/daemon.json establishes baseline security standards globally across all running containers, preventing rogue unhardened container instances.
Rather than relying solely on individual developer discipline in Docker Compose files, CIS Docker Benchmark section 2 mandates hardening the central Docker daemon configuration. The configuration file at /etc/docker/daemon.json governs logging drivers, inter-container communication, and default runtime behaviors.
A fully compliant CIS-hardened daemon configuration includes:
{
"icc": false,
"no-new-privileges": true,
"userland-proxy": false,
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
},
"storage-driver": "overlay2",
"userns-remap": "default"
}
Disabling inter-container communication ("icc": false") prevents containers attached to the default bridge network from snooping on each other’s traffic or initiating unauthorized lateral connections. Setting "live-restore": true ensures that the Docker daemon can be patched or restarted without terminating running containers, maintaining high availability during kernel security maintenance.
Hardening Docker Storage Drivers and Preventing Fork Bombs
A classic vulnerability in unhardened Docker environments is the fork bomb: a malicious script or compromised microservice repeatedly invokes fork() in an infinite loop. Without explicit control group constraints, the process tree rapidly exhausts the Linux host’s pid_max limit, freezing the entire bare-metal operating system and locking administrators out of SSH sessions.
CIS Docker Benchmark Section 4 explicitly requires setting process limits on all container executions. This can be configured globally in /etc/docker/daemon.json or declared per container in Docker Compose files:
services:
web-api:
image: company/web-api:1.4.2
deploy:
resources:
limits:
cpus: '2.0'
memory: 1024M
pids: 100
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid
Restricting pids: 100 ensures that even if an attacker achieves arbitrary code execution inside the container and attempts a fork bomb, the Linux kernel cgroup controller throttles process creation at the 100-process threshold, neutralizing the denial of service attempt while leaving other containers and host daemons completely unharmed.
Frequently Asked Questions
Does running Docker as rootless degrade container performance?
Rootless Docker introduces negligible CPU overhead (less than 1%). However, because rootless networking relies on user-mode network stacks (slirp4netns or rootlesskit), high-throughput network operations can see a 10% to 15% throughput penalty compared to native kernel bridge networks.
How do you verify your Docker installation against the CIS Benchmark?
You can execute the official open-source docker-bench-security script. The automated auditing tool evaluates your daemon.json, container configurations, and host kernel settings against hundreds of CIS test criteria, generating a pass/fail compliance scorecard.
Why is the privileged flag (–privileged) so dangerous?
The --privileged flag completely disables all AppArmor, Seccomp, and capability restrictions, granting the container direct access to all host kernel devices in /dev. A privileged container can mount host hard drives and escape into the host operating system within two commands.

