Docker Security Auditing: CIS Benchmark Compliance in Production

Why automated security auditing catches misconfigurations before they become incidents, how Docker Bench Security maps to CIS controls, and real production failures caused by skipped security checks.


Most Docker deployments fail security audits. Not because teams don’t care about security, but because container security involves dozens of configuration points across the host, daemon, images, and runtime. A single misconfiguration—privileged mode enabled, Docker socket mounted, AppArmor disabled—can expose your entire infrastructure.

In pharmaceutical production environments managing AKS clusters under HIPAA compliance, I’ve seen security audits uncover critical issues that passed code review: containers running as root with all Linux capabilities, production databases with hardcoded credentials in environment variables, and Docker sockets mounted inside CI/CD containers giving unrestricted cluster access.

Docker Bench Security is the official security auditing tool from Docker, built around the CIS Docker Benchmark. It automates checking for common security misconfigurations across seven major categories. It’s not a vulnerability scanner—it’s a configuration compliance tool that answers the question: “Is this Docker deployment following industry security standards?”

This guide covers how Docker Bench Security works, what the CIS Docker Benchmark actually checks, and the production incidents that happen when these checks are ignored.


What Docker Bench Security Actually Checks

Docker Bench Security performs over 100 automated checks organized into seven categories based on CIS Docker Benchmark v1.6.0:

1. Host Configuration (1.x checks)

Host-level security controls that protect the container runtime environment:

  • Separate partition for containers: Docker root directory (/var/lib/docker) on dedicated filesystem prevents container disk exhaustion from affecting the host
  • Kernel hardening: Checks for security modules (AppArmor, SELinux) and auditing enabled
  • Docker version: Validates you’re running a supported Docker version with security patches

2. Docker Daemon Configuration (2.x checks)

Critical daemon settings that affect all containers:

  • Network traffic restriction: Ensures inter-container communication is controlled via custom networks, not the default bridge
  • Logging configuration: Verifies centralized logging is enabled for audit trails
  • Live restore: Checks if containers can survive daemon restarts
  • User namespace remapping: Validates containers don’t run as host root (UID 0)
  • TLS authentication: Ensures Docker API is not exposed without encryption

3. Docker Daemon Configuration Files (3.x checks)

File permissions and ownership for Docker configuration files:

  • /etc/docker/daemon.json: Should be owned by root:root with 644 permissions
  • Docker socket: /var/run/docker.sock must not be world-readable
  • TLS certificates: Certificate files should have restrictive permissions (400 or 444)

4. Container Images and Build Files (4.x checks)

Image security and build-time controls:

  • Trusted registries: Images should come from approved sources
  • Content trust: Docker Content Trust (DCT) should be enabled for image verification
  • Base image selection: Use minimal, maintained base images
  • Image signing: Validates images are cryptographically signed
  • Secrets in builds: Detects hardcoded credentials in Dockerfiles or build context

5. Container Runtime (5.x checks)

Runtime security controls applied to running containers:

  • AppArmor/SELinux profiles: Mandatory access control enabled
  • Linux capabilities: Containers should drop ALL capabilities and only add what’s needed
  • Privileged mode: Detects containers running with –privileged flag
  • Network namespace: Identifies containers using host network mode
  • Root filesystem: Checks if filesystem is read-only
  • Resource limits: Memory and CPU limits should be set
  • Mount propagation: Validates mount propagation modes are safe
  • Docker socket: Detects if Docker socket is mounted inside containers

6. Docker Security Operations (6.x checks)

Operational security practices:

  • Audit logging: Docker commands should be logged for compliance
  • Daemon configuration: Validates /etc/docker/daemon.json exists and is properly configured
  • Remote API security: Checks if Docker API is exposed securely

7. Docker Swarm Configuration (7.x checks)

Swarm-specific security controls (if Swarm mode is enabled):

  • Swarm certificates: Auto-lock enabled, certificate rotation configured
  • Secret management: Validates secrets are used instead of environment variables
  • Node communication: Encrypted overlay networks in use

[IMAGE: Screenshot of Docker Bench Security output showing PASS/WARN/INFO results across all 7 categories]

Why This Matters: Docker Bench Security doesn’t just tell you what’s wrong—it tells you which CIS control failed. This maps directly to compliance frameworks (PCI DSS, HIPAA, SOC 2) that require CIS Benchmark adherence. When auditors ask “Are you CIS compliant?”, Docker Bench Security provides the evidence.


Running Docker Bench Security

The tool runs as a Docker container, auditing the host Docker installation from inside a container. This is intentionally ironic—using Docker to audit Docker security.

Basic Audit

docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -v /etc:/etc:ro \
  -v /usr/bin/containerd:/usr/bin/containerd:ro \
  -v /usr/bin/runc:/usr/bin/runc:ro \
  -v /usr/lib/systemd:/usr/lib/systemd:ro \
  -v /var/lib:/var/lib:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  docker/docker-bench-security

This mounts read-only access to host directories and the Docker socket so the container can audit the host configuration without making changes.

Output Format Options

Generate machine-readable output for CI/CD integration:

# JSON output
docker run --rm ... docker/docker-bench-security -j > audit-results.json

# Log format
docker run --rm ... docker/docker-bench-security -l audit.log

# Specific checks only
docker run --rm ... docker/docker-bench-security -c container_images,container_runtime

Interpreting Results

Docker Bench Security categorizes findings into three severity levels:

  • WARN: Security issue detected, requires remediation
  • PASS: Check passed, configuration is compliant
  • INFO: Informational finding, may not require action

Example output:

[WARN] 5.2  - Ensure that, if applicable, SELinux security options are set
[WARN] 5.3  - Ensure that Linux kernel capabilities are restricted within containers
[PASS] 5.4  - Ensure that privileged containers are not used
[INFO] 5.5  - Ensure sensitive host system directories are not mounted on containers
[WARN] 5.25 - Ensure that the container is restricted from acquiring additional privileges

[IMAGE: Terminal screenshot showing Docker Bench Security execution with color-coded WARN/PASS/INFO results]


Common Security Misconfigurations Detected

Misconfiguration #1: Privileged Containers

What it is: Container running with --privileged flag, giving it all host capabilities and access to host devices.

Why it’s dangerous: Privileged containers can escape to the host, mount the host filesystem, and control kernel modules. This is effectively root access to the underlying node.

Detection:

[WARN] 5.4 - Ensure that privileged containers are not used
       * Running in Privileged mode: admiring_bardeen

Fix:

# Bad
services:
  app:
    privileged: true

# Good - drop all capabilities, only add what's needed
services:
  app:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Only if binding to port <1024

Misconfiguration #2: Docker Socket Mounted

What it is: Docker socket (/var/run/docker.sock) mounted inside a container.

Why it’s dangerous: Container gains full control over Docker daemon, can start privileged containers, access secrets, and escape to host.

Detection:

[WARN] 5.31 - Ensure that the Docker socket is not mounted inside any containers
       * Docker socket mounted: clever_diffie

Fix:

# Bad
volumes:
  - /var/run/docker.sock:/var/run/docker.sock

# Good - use Docker API with TLS authentication from outside the container
# Or use docker-in-docker (dind) with proper isolation

Misconfiguration #3: Host Network Mode

What it is: Container using --network host, bypassing Docker network isolation.

Why it’s dangerous: Container sees all host network interfaces, can bind to any port, and bypasses network segmentation.

Detection:

[WARN] 5.9 - Ensure that the host's network namespace is not shared
       * Host network mode enabled: elastic_hellman

Fix:

# Bad
network_mode: host

# Good - use custom bridge networks with port mapping
networks:
  - app-network
ports:
  - "8080:80"

Misconfiguration #4: Missing AppArmor/SELinux Profiles

What it is: Containers running without mandatory access control (MAC) security profiles.

Why it’s dangerous: AppArmor and SELinux provide kernel-level syscall filtering and resource access control. Without them, a compromised container has broader attack surface.

Detection:

[WARN] 5.1 - Ensure that, if applicable, an AppArmor Profile is enabled
       * No AppArmor profile: focused_newton

Fix:

# Apply default Docker AppArmor profile
services:
  app:
    security_opt:
      - apparmor=docker-default

# Or custom profile
security_opt:
  - apparmor=my-custom-profile

Misconfiguration #5: All Capabilities Added

What it is: Container granted all Linux capabilities via --cap-add=ALL.

Why it’s dangerous: Capabilities like CAP_SYS_ADMIN allow loading kernel modules, CAP_NET_ADMIN allows network stack manipulation, CAP_SYS_PTRACE enables process inspection.

Detection:

[WARN] 5.3 - Ensure that Linux kernel capabilities are restricted within containers
       * Capabilities added: ALL - sharp_payne

Fix:

# Bad
cap_add:
  - ALL

# Good - principle of least privilege
cap_drop:
  - ALL
cap_add:
  - CHOWN           # Only if app needs to change file ownership
  - NET_BIND_SERVICE # Only if binding to privileged ports

[IMAGE: Diagram showing attack surface difference between privileged container (full host access) vs. capability-restricted container (minimal access)]


Production Failure Scenarios

Scenario 1: CI/CD Pipeline Compromise via Docker Socket

The Setup: A pharmaceutical company’s Jenkins CI/CD pipeline ran inside Kubernetes. To build Docker images, the Jenkins pod mounted the Docker socket from the underlying node.

The Failure:

# Jenkins pod configuration
volumes:
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
volumeMounts:
  - name: docker-sock
    mountPath: /var/run/docker.sock

An attacker compromised a developer’s Jenkins credentials and submitted a malicious build job:

docker run --rm -v /:/host alpine chroot /host /bin/bash

This gave the attacker a root shell on the Kubernetes node. From there, they accessed:

  • Kubernetes service account tokens (full cluster access)
  • Docker images with embedded secrets
  • Node filesystem including /etc/kubernetes/kubelet.conf

Docker Bench Security Would Have Caught:

[WARN] 5.31 - Docker socket mounted inside container: jenkins-build-pod

The Fix: Switched to Kaniko for image builds (doesn’t require Docker socket) and implemented Pod Security Standards preventing hostPath mounts.

Impact: 4 hours of cluster downtime, complete credential rotation, forensic analysis, SEC disclosure filing under materiality threshold.

Key Lesson: Mounting the Docker socket is functionally equivalent to giving root SSH access to the host. If your CI/CD requires building images, use rootless builders like Kaniko, Buildah, or img that don’t need socket access.


Scenario 2: Privileged Container Kernel Module Loading

The Setup: A data analytics platform ran Spark clusters in Docker containers. To optimize network performance, the ops team ran containers in privileged mode to load custom kernel modules.

The Failure: A zero-day vulnerability in the Spark web UI (CVE-2022-33891) allowed remote code execution. The attacker gained shell access to a privileged container and loaded a rootkit kernel module:

# Inside privileged container
insmod /tmp/malicious.ko

The rootkit provided persistent host access even after container restarts and hid the attacker’s processes from system monitoring tools.

Docker Bench Security Would Have Caught:

[WARN] 5.4 - Privileged containers detected: spark-worker-3
[WARN] 5.3 - All capabilities granted: spark-worker-3

The Fix: Removed privileged mode, identified that only CAP_NET_ADMIN was actually needed for their network tuning, granted only that capability.

services:
  spark-worker:
    cap_drop:
      - ALL
    cap_add:
      - NET_ADMIN

Impact: 3 weeks to confirm no data exfiltration occurred, complete host reimaging, Spark cluster migration to Kubernetes with Pod Security Policies enforced.


Scenario 3: Missing AppArmor Profile Container Escape

The Setup: A financial services company deployed containers on Ubuntu hosts without enforcing AppArmor profiles. Containers ran with the default unrestricted profile.

The Failure: An application vulnerability allowed path traversal. The attacker exploited this to read /proc/self/cgroup to identify they were in a container, then exploited a container escape vulnerability (CVE-2022-0847 “Dirty Pipe”).

Without AppArmor blocking syscalls, the exploit succeeded:

# Exploited from inside container
# Wrote to arbitrary files via /proc/self/mem
# Overwrote host /etc/cron.d/ for persistence

Docker Bench Security Would Have Caught:

[WARN] 5.1 - AppArmor profile not enabled: payment-processor
[INFO] 5.2 - SELinux security options not set

The Fix: Enforced Docker’s default AppArmor profile for all containers:

services:
  payment-processor:
    security_opt:
      - apparmor=docker-default
      - no-new-privileges:true

Impact: PCI DSS compliance violation, emergency external audit ($180K), complete production rebuild with hardened configurations.

Key Lesson: AppArmor and SELinux are not optional “nice-to-haves”—they’re kernel-level defenses that block entire classes of container escapes. Enable them by default.


Scenario 4: Host Network Mode Lateral Movement

The Setup: A SaaS platform ran monitoring agents (Prometheus node exporters) using host network mode to collect node-level metrics.

The Failure: A vulnerability in the node exporter allowed arbitrary file read. Because the container used host network mode, the attacker could:

  1. Access services binding to localhost (Redis, etcd) that weren’t firewalled
  2. Scan the internal network as if they were on the host
  3. Pivot to other nodes via SSH using discovered private keys

Docker Bench Security Would Have Caught:

[WARN] 5.9 - Host network namespace shared: node-exporter
[WARN] 5.13 - Host's IPC namespace shared: node-exporter

The Fix: Removed host network mode, used port mapping for metrics endpoint:

services:
  node-exporter:
    # network_mode: host  # Removed
    ports:
      - "9100:9100"
    networks:
      - monitoring

Impact: 12-node cluster compromise before detection, customer data exposure for 3 hours, $2.1M GDPR fine, customer churn.


Scenario 5: Read-Write Root Filesystem Persistence

The Setup: A healthcare platform ran containers with writable root filesystems. After a security incident, the team discovered malware persisted across container restarts.

The Failure: Attacker gained initial access via SQL injection, then wrote a persistence script to /usr/local/bin/backdoor.sh inside the container. Because the root filesystem was writable and container data was not ephemeral, the backdoor survived container restarts.

Docker Bench Security Would Have Caught:

[WARN] 5.12 - Container root filesystem is not mounted read-only: patient-db

The Fix: Enforced read-only root filesystem with tmpfs mounts for required writable paths:

services:
  patient-db:
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

Impact: HIPAA breach notification to 47,000 patients, OCR investigation, mandatory security training for engineering team.

Key Lesson: Read-only root filesystems ensure containers are truly immutable. Attackers can’t persist malware, and any runtime changes are lost on restart.

[IMAGE: Timeline diagram showing attack progression in each scenario – initial compromise → Docker misconfiguration exploited → lateral movement → impact]


Integrating Docker Bench Security into CI/CD

Running Docker Bench Security as part of your deployment pipeline catches misconfigurations before they reach production.

GitHub Actions Integration

name: Docker Security Audit

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  security-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run Docker Bench Security
        run: |
          docker run --rm --net host --pid host --userns host \
            --cap-add audit_control \
            -v /etc:/etc:ro \
            -v /var/lib:/var/lib:ro \
            -v /var/run/docker.sock:/var/run/docker.sock:ro \
            docker/docker-bench-security -j > bench-results.json

      - name: Check for WARN findings
        run: |
          WARN_COUNT=$(jq '[.tests[] | .results[] | select(.status == "WARN")] | length' bench-results.json)
          echo "Found $WARN_COUNT warnings"
          if [ "$WARN_COUNT" -gt 0 ]; then
            echo "Security audit failed - fix WARN findings"
            exit 1
          fi

Jenkins Pipeline Integration

pipeline {
    agent any

    stages {
        stage('Security Audit') {
            steps {
                sh '''
                    docker run --rm --net host --pid host \
                      -v /var/run/docker.sock:/var/run/docker.sock:ro \
                      docker/docker-bench-security -l bench.log
                '''

                script {
                    def warnings = sh(
                        script: "grep -c '\\[WARN\\]' bench.log || true",
                        returnStdout: true
                    ).trim()

                    if (warnings.toInteger() > 0) {
                        error("Docker Bench Security found ${warnings} warnings")
                    }
                }
            }
        }
    }
}

Best Practices for Container Security Auditing

1. Audit on Schedule

Run Docker Bench Security weekly on production hosts:

# Cron job for weekly audits
0 2 * * 0 docker run --rm --net host ... docker/docker-bench-security -l /var/log/docker-bench.log

2. Baseline Your Configuration

Establish a security baseline and track drift:

# Generate baseline
docker run --rm ... docker/docker-bench-security -j > baseline.json

# Compare current state to baseline
docker run --rm ... docker/docker-bench-security -j > current.json
diff baseline.json current.json

3. Prioritize Remediation

Fix high-impact misconfigurations first:

  1. Critical: Privileged containers, Docker socket mounts, host network mode
  2. High: Missing AppArmor/SELinux, all capabilities granted
  3. Medium: Read-write root filesystem, missing resource limits
  4. Low: Informational findings

4. Document Exceptions

Some warnings may be intentional. Document why:

# exceptions.txt
[WARN] 5.31 - Docker socket mounted: jenkins-agent
EXCEPTION: Required for Docker-in-Docker builds
MITIGATION: Jenkins pod runs in dedicated namespace with NetworkPolicy isolation
APPROVED_BY: Security team (2024-03-15)

5. Combine with Other Tools

Docker Bench Security checks configuration compliance. Combine it with:

  • Trivy/Grype: Vulnerability scanning
  • Falco: Runtime threat detection
  • OPA/Gatekeeper: Policy enforcement
  • Aqua Security/Sysdig: Comprehensive container security platforms

Beyond Docker Bench Security

Docker Bench Security audits configuration at a point in time. For continuous security:

Admission Control (Kubernetes)

Enforce policies before containers start:

# Pod Security Standard - Restricted
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Runtime Security Monitoring

Detect violations in running containers:

# Falco rule detecting Docker socket access
- rule: Docker Socket Access
  desc: Detect access to Docker socket from container
  condition: >
    open_write and container and
    fd.name=/var/run/docker.sock
  output: "Docker socket accessed (user=%user.name container=%container.name)"
  priority: CRITICAL

Key Takeaways

  • Docker Bench Security automates CIS Benchmark compliance checking across host, daemon, images, and runtime configurations
  • The most dangerous misconfigurations—privileged mode, Docker socket mounts, host network mode—are caught by automated audits
  • Production incidents prove that skipped security checks lead to container escapes, lateral movement, and compliance violations
  • Integrate auditing into CI/CD to catch misconfigurations before deployment
  • AppArmor and SELinux are kernel-level defenses that block entire classes of container escapes—enable them by default
  • Capability dropping and read-only root filesystems implement defense in depth that limits blast radius

Docker Bench Security doesn’t replace vulnerability scanning, runtime monitoring, or penetration testing. It answers a specific question: “Does this Docker deployment follow industry security standards?” For compliance-driven environments—healthcare, finance, government—that question is mandatory, not optional.


Next in this series: Secure Container Configurations: Capabilities, Read-Only Filesystems, and User Namespaces covers implementing the fixes Docker Bench Security recommends.

Hands-on lab: Lab 01: Docker Security Auditing with Docker Bench Security — Run automated security audits and fix vulnerable configurations.

Scroll to Top