Docker 96 🐳 Docker Engine Benchmarking: CIS Docker Benchmark Verification and Compliance Auditing
The CIS Docker Benchmark is a vendor-neutral hardening standard published by the Center for Internet Security, currently at version 1.6.0, with over 100 recommendations organized into seven sections covering the host, daemon, configuration files, images, runtime, operations, and Swarm . Auditors for SOC 2 Type II, PCI DSS 4.0, and FedRAMP accept CIS Docker scores as evidence of secure configuration, which means a failed Scored check—like running containers as root or using privileged mode—can delay a deal cycle or trigger a re-audit . The verification tool is Docker Bench for Security, an open-source script that automates the checks and produces pass/fail/warn results .
Key point: The published docker/docker-bench-security image is out of date. The maintainers recommend cloning the repository and building the image locally before running the audit .
Why the CIS Benchmark is the reference standard
The specificity problem. Broader frameworks like NIST 800-190 describe container security goals but leave the implementation open. CIS provides numeric control IDs—4.1 for “ensure a user for the container has been created,” 5.4 for “do not use privileged containers”—which makes it testable and citable in a control narrative . A SOC 2 report that says “we follow CIS Docker Benchmark v1.6.0, Level 1” is making a claim that an auditor can verify.
The default-behavior problem. The controls that fail most often are not exotic misconfigurations; they are defaults. Control 4.1 fails because Docker images ship running as UID 0 unless a USER directive is added. Control 5.4 fails because CI runners and legacy deployment scripts default to --privileged for Docker-in-Docker builds. Control 2.1 fails because inter-container communication is permissive by default . The benchmark catches what ad hoc reviews miss because it requires an explicit override for each default.
The layered-audit problem. A clean image can still run on a daemon that fails host-level checks. The benchmark covers four distinct layers: host OS (kernel version, separate partition for /var/lib/docker, auditd rules), daemon configuration (TLS, userns-remap, seccomp), image hygiene (no root user, no secrets in layers, content trust), and runtime behavior (no privileged containers, no docker.sock mounts, resource limits) . A “pass CIS” claim without specifying which sections were assessed is meaningless.
a. Running Docker Bench for Security
The script must run with host network, PID, and user namespaces, and it needs read-only access to the Docker socket and the host’s configuration directories. It also requires the audit_control capability to run the auditd checks .
# Clone and build the current image
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
docker build --no-cache -t docker-bench-security .
# Run the full audit
docker run --rm \
--net host --pid host --userns host \
--cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /lib/systemd/system:/lib/systemd/system:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker-bench-security
The volume mounts vary by distribution. On Ubuntu, docker.service lives in /lib/systemd/system; on other systems, it may be in /usr/lib/systemd . The script accepts -c to run specific check sections, -e to exclude specific checks, and -l to write the output to a file for JSON parsing .
# Run only container runtime checks
docker run --rm --net host --pid host --userns host \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
docker-bench-security -c container_runtime
# JSON output for CI integration
docker run ... docker-bench-security -l /dev/stdout 2>/dev/null | tee results.json
b. Interpreting the Results
The output uses four result types: [PASS], [WARN], [INFO], and [FAIL] . The [WARN] results are the ones that need attention—they indicate a check that failed or a configuration that does not meet the benchmark’s recommendation.
| Finding | Risk | Fix |
|---|---|---|
| Container running as root | Medium | Add USER to Dockerfile or --user to run |
| Privileged container found | Critical | Remove --privileged; add specific capabilities |
| Docker socket mounted | Critical | Remove mount or use socket proxy |
| No memory limit set | Low | Add --memory or compose mem_limit |
| No health check defined | Low | Add HEALTHCHECK to Dockerfile |
| Host network mode used | High | Use port mapping and user-defined networks |
| SYS_ADMIN capability added | High | Remove from cap_add; drop all and add only needed |
The critical findings—privileged containers, docker.sock mounts—are also container-escape vectors. A container with the Docker socket mounted can control the daemon, which is equivalent to root on the host . The audit flags these because they are both compliance failures and security failures.
c. Remediation and Continuous Compliance
The daemon configuration is where most of the security gains are realized. The daemon.json file should set icc: false to restrict inter-container communication, userns-remap to map container root to an unprivileged host user, no-new-privileges to prevent privilege escalation, and log rotation to prevent disk exhaustion .
{
"icc": false,
"userns-remap": "default",
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
Container runtime hardening is handled at the docker run level or in the compose file. The pattern is to drop all capabilities and add back only what the process needs, mount the root filesystem as read-only with tmpfs for writable paths, and apply a seccomp profile .
services:
app:
image: myapp:1.4.2
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
Continuous compliance requires running the audit on a schedule and alerting on [WARN] results. The script can be run from a cron job or a CI pipeline, and the JSON output can be compared against a baseline to detect drift .
# Daily audit
0 2 * * * /usr/local/bin/docker-bench-security.sh > /var/log/docker-audit.log
# Alert on failures
if grep -q "WARN" /var/log/docker-audit.log; then send-alert; fi
Complete Example Session
# ============================================
# PART 1: BUILD THE CURRENT AUDIT IMAGE
# ============================================
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
docker build --no-cache -t docker-bench-security .
# ============================================
# PART 2: RUN THE FULL AUDIT
# ============================================
docker run --rm --net host --pid host --userns host \
--cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /lib/systemd/system:/lib/systemd/system:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker-bench-security
# ============================================
# PART 3: IDENTIFY WARN RESULTS
# ============================================
# Look for [WARN] lines
# Common: 4.1 (root user), 5.4 (privileged), 5.9 (host network)
# ============================================
# PART 4: REMEDIATE DAEMON CONFIG
# ============================================
# Edit /etc/docker/daemon.json
{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker
# ============================================
# PART 5: REMEDIATE CONTAINER RUNTIME
# ============================================
docker run -d --name app \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
myapp:1.4.2
# ============================================
# PART 6: REMEDIATE DOCKERFILE
# ============================================
# Add non-root user before the final CMD
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["node", "server.js"]
# ============================================
# PART 7: RE-RUN THE AUDIT
# ============================================
# Compare results to the baseline
# The [WARN] count should decrease
# ============================================
# PART 8: SCHEDULE CONTINUOUS AUDIT
# ============================================
# Cron: daily at 2 AM
0 2 * * * /usr/local/bin/docker-bench-security.sh > /var/log/docker-audit.log
# Alert on WARN
if grep -q "WARN" /var/log/docker-audit.log; then send-alert; fi
# ============================================
# PART 9: VERIFY SPECIFIC SECTIONS
# ============================================
docker run ... docker-bench-security -c container_runtime,container_images
# ============================================
# PART 10: EXPORT FOR CI
# ============================================
docker run ... docker-bench-security -l /dev/stdout 2>/dev/null | tee results.json
The ten parts trace the audit from build through remediation to continuous monitoring. Part 3 shows the failure identification, and Parts 4 through 6 show the three layers of remediation: daemon, runtime, and image.
Quick Reference
CIS Docker Benchmark Sections
| Section | Scope | Key Checks |
|---|---|---|
| 1 | Host configuration | Separate partition, auditd, kernel |
| 2 | Docker daemon | TLS, userns-remap, icc, seccomp |
| 3 | Daemon files | File permissions, ownership |
| 4 | Container images | Non-root USER, no secrets, content trust |
| 5 | Container runtime | No privileged, no host network, limits |
| 6 | Security operations | Audit logging, API security |
| 7 | Swarm | Swarm mode security |
Common Failures
| Check | Failure | Fix |
|---|---|---|
| 4.1 | Container runs as root | USER in Dockerfile |
| 5.4 | Privileged container | Remove --privileged |
| 5.9 | Host network mode | Use port mapping |
| 2.1 | ICC enabled | "icc": false in daemon.json |
| 5.12 | Writable rootfs | --read-only |
| 4.6 | No HEALTHCHECK | Add to Dockerfile |
Audit Commands
| Command | Purpose |
|---|---|
docker-bench-security | Full audit |
-c section | Run specific sections |
-e check | Exclude specific checks |
-l file | Write output to file |
-b | Disable colors |
Best Practices
✅ Do This:
# Build the current image before auditing
git clone ... && docker build --no-cache -t docker-bench-security . # ✅
# Run with host namespaces and audit_control capability
--net host --pid host --userns host --cap-add audit_control # ✅
// Set icc false and userns-remap in daemon.json
{ "icc": false, "userns-remap": "default" } // ✅
// Add USER directive to every Dockerfile
USER app // ✅
❌ Don’t Do This:
# Use the outdated published image
docker run docker/docker-bench-security # out of date // ❌
# Assume a clean image passes the audit
# Daemon and host checks are separate // ❌
# Run the audit once and assume it stays compliant
# Drift occurs; schedule continuous audits // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Audit image is stale | Published image not maintained | Build from source |
audit_control missing | Checks for auditd fail | Add the capability |
| Volume mount wrong | docker.service path differs by distro | Adjust mount path |
| Remediation breaks app | Dropped required capability | Add back only needed caps |
--privileged still needed | CI runner uses Docker-in-Docker | Use rootless or socket proxy |
Real-World Examples
1. Build Audit Image
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security && docker build --no-cache -t docker-bench-security .
2. Run Full Audit
docker run --rm --net host --pid host --userns host --cap-add audit_control -v /var/run/docker.sock:/var/run/docker.sock:ro docker-bench-security
3. Restrict ICC
{ "icc": false }
4. Enable Userns Remap
{ "userns-remap": "default" }
5. Non-Root User
USER app
6. Read-Only Rootfs
docker run --read-only --tmpfs /tmp myapp
7. Drop Capabilities
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
8. Schedule Audit
0 2 * * * /usr/local/bin/docker-bench-security.sh > /var/log/docker-audit.log
9. Run Specific Section
docker-bench-security -c container_runtime
10. JSON Export
docker-bench-security -l /dev/stdout 2>/dev/null | tee results.json
Visual
The Seven Sections
┌──────────────────────────────────────────────────────────────┐
│ CIS DOCKER BENCHMARK v1.6.0 │
│ │
│ 1. Host Configuration │
│ └─ Partitions, auditd, kernel │
│ │
│ 2. Docker Daemon Configuration │
│ └─ TLS, userns-remap, icc, seccomp │
│ │
│ 3. Daemon Configuration Files │
│ └─ Permissions, ownership │
│ │
│ 4. Container Images & Build Files │
│ └─ Non-root USER, no secrets, content trust │
│ │
│ 5. Container Runtime │
│ └─ Privileged, host network, limits, read-only │
│ │
│ 6. Docker Security Operations │
│ └─ Audit logging, API security │
│ │
│ 7. Docker Swarm Configuration │
│ └─ Swarm mode security │
│ │
│ A clean image can still fail host and daemon checks. │
│ │
└──────────────────────────────────────────────────────────────┘
The Audit Pipeline
┌──────────────────────────────────────────────────────────────┐
│ docker-bench-security │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Read daemon.json│ │
│ │ docker info │ │
│ │ docker inspect │ │
│ │ File stats │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Run checks │ │
│ │ per section │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Output │ │
│ │ [PASS] [WARN] │ │
│ │ [INFO] [FAIL] │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Remediate │ │
│ │ Re-run │ │
│ └─────────────────┘ │
│ │
│ Read-only tool. It never modifies the host. │
│ │
└──────────────────────────────────────────────────────────────┘
The Three Remediation Layers
┌──────────────────────────────────────────────────────────────┐
│ LAYER 1: DAEMON │
│ /etc/docker/daemon.json │
│ ├─ icc: false │
│ ├─ userns-remap: default │
│ └─ no-new-privileges: true │
│ │
│ LAYER 2: CONTAINER RUNTIME │
│ docker run / compose │
│ ├─ --read-only │
│ ├─ --cap-drop ALL │
│ └─ --security-opt no-new-privileges │
│ │
│ LAYER 3: IMAGE │
│ Dockerfile │
│ ├─ USER app │
│ ├─ Minimal base │
│ └─ No secrets in layers │
│ │
│ Each layer is independent. All three must pass. │
│ │
└──────────────────────────────────────────────────────────────┘
Continuous Compliance
┌──────────────────────────────────────────────────────────────┐
│ SCHEDULE (cron) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Run audit │ │
│ │ daily at 2 AM │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Check for WARN │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ ▼ ▼ │
│ ┌──────┐ ┌──────┐ │
│ │ None │ │ WARN │ │
│ │ │ │ found│ │
│ │ Sleep│ │ Alert│ │
│ └──────┘ └──────┘ │
│ │
│ Drift happens. The audit must run continuously. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Benchmark | CIS Docker Benchmark v1.6.0 |
| Controls | 100+ across 7 sections |
| Audit tool | Docker Bench for Security |
| Tool requirement | Host network, PID, userns, audit_control |
| Published image | Out of date; build from source |
| Result types | [PASS], [WARN], [INFO], [FAIL] |
| Most common failures | Root user, privileged, host network |
| Daemon fix | icc: false, userns-remap, no-new-privileges |
| Runtime fix | --read-only, --cap-drop ALL |
| Image fix | USER directive, minimal base |
Key takeaways:
- The CIS Docker Benchmark is the reference standard for Docker hardening. It is specific enough to be testable and is cited directly by SOC 2, PCI DSS, and FedRAMP auditors. The 100+ controls span the host, daemon, images, and runtime, and a failure at any layer is a failure of the deployment .
- Docker Bench for Security is the audit tool, but the published image is stale. The repository must be cloned and the image built locally before running the audit. The script requires host namespaces and the
audit_controlcapability to check the host-level controls . - The most common failures are defaults, not exotic misconfigurations. Containers running as root, privileged mode used for CI, host network mode used for service discovery, and inter-container communication left permissive. Each has a specific fix, and each fix is a configuration override .
- Remediation is layered. The daemon configuration, the container runtime flags, and the Dockerfile each address different controls. A clean image does not compensate for a permissive daemon, and a hardened daemon does not compensate for a privileged container.
- Compliance is continuous, not one-time. Configurations drift, new containers are deployed, and new CVEs are published. The audit must run on a schedule, and the results must be compared against a baseline to detect drift .
Remember: The CIS Docker Benchmark is a checklist, not a scanner. It tells you what a reasonably hardened Docker deployment looks like, and Docker Bench tells you which of those recommendations your deployment fails. The tool is read-only; the fixes are manual or automated through configuration management. The benchmark’s value is that it makes the definition of “hardened” specific enough to audit and specific enough to remediate. The controls that fail most often—root users, privileged containers, host networking—are the ones that are easiest to leave at their defaults and the ones that carry the highest risk. The audit exists to surface them before an attacker does.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!