Docker Bench for Security is a shell script maintained by Docker that checks a host against the CIS Docker Benchmark, a public list of recommendations for securing the Docker daemon, its files, images and running containers. It does not change anything: it reports each check as passed or as a warning, so you can decide what to fix. In this tutorial you will run Docker Bench on Ubuntu 24.04, read its report, apply fixes for the most common warnings in the daemon, host audit rules and container runtime, and schedule a weekly audit.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS with Docker Engine installed from Docker's official repository, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • git installed (sudo apt install git).

Docker Bench inspects running containers, so results are more meaningful on a host that already runs your workloads. Try the daemon changes on a test server first, since some of them affect every container.

Step 1 - Downloading Docker Bench

Docker also publishes a docker/docker-bench-security image, but it has not been rebuilt in years and runs outdated checks. Run the script from the Git repository instead, which follows the current benchmark:

cd ~
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security

The script only needs standard tools (awk, grep, sed, stat) and the Docker CLI. It must run as root because it reads daemon files and inspects every container.

Step 2 - Running the first audit

Run the full audit:

sudo sh docker-bench-security.sh

The output is grouped by the sections of the CIS benchmark. Each check is labeled with a status:

[INFO] 1 - Host Configuration
[INFO] 1.1 - Linux Hosts Specific Configuration
[WARN] 1.1.1 - Ensure a separate partition for containers has been created (Automated)
[INFO] 1.1.2 - Ensure only trusted users are allowed to control Docker daemon (Automated)
[INFO]       * Users: your_user
[WARN] 1.1.3 - Ensure auditing is configured for the Docker daemon (Automated)
...
[INFO] 2 - Docker daemon configuration
[WARN] 2.2 - Ensure network traffic is restricted between containers on the default bridge (Manual)
[PASS] 2.3 - Ensure the logging level is set to 'info' (Manual)
...
[INFO] Checks: 117
[INFO] Score: 4

The labels mean:

StatusMeaning
PASSThe host follows the recommendation.
WARNThe recommendation is not met. Review it.
INFOInformation to review manually, such as the list of users in the docker group.
NOTEA check the script cannot verify automatically.

The final score adds one point per PASS and subtracts one per WARN. Check numbers change between benchmark versions, so rely on the check title rather than the number.

The script also saves the report to log/docker-bench-security.log and a machine-readable version to log/docker-bench-security.log.json inside the repository directory.

Step 3 - Reading the report

A first run usually produces dozens of warnings. Not all of them apply to every server, so sort them into three groups:

  1. Fix now: cheap changes with clear security value, such as audit rules, daemon options and container flags. This tutorial covers these.
  2. Plan: changes that need downtime or testing, such as moving /var/lib/docker to its own partition or enabling user namespace remapping.
  3. Accept: checks that do not fit your setup. For example, enabling Docker Content Trust breaks pulls of unsigned images, and a host that is not part of a Swarm can ignore the Swarm section.

List only the warnings to get a compact to-do list:

grep WARN log/docker-bench-security.log

To re-run a single section while you work, pass its name with -c. The main section names are host_configuration, docker_daemon_configuration, docker_daemon_files, container_images, container_runtime and docker_security_operations:

sudo sh docker-bench-security.sh -c docker_daemon_configuration

Step 4 - Adding audit rules for Docker

Several host checks warn that Docker files are not audited. Auditing records every change to the daemon binary, its configuration and its data directory, which helps investigate an incident. Install the Linux audit daemon:

sudo apt install auditd

Create a rules file for Docker:

sudo nano /etc/audit/rules.d/docker.rules
-w /usr/bin/dockerd -k docker
-w /usr/bin/containerd -k docker
-w /usr/bin/runc -k docker
-w /var/lib/docker -k docker
-w /etc/docker -k docker
-w /etc/default/docker -k docker
-w /etc/docker/daemon.json -k docker
-w /usr/lib/systemd/system/docker.service -k docker
-w /usr/lib/systemd/system/docker.socket -k docker
-w /run/containerd -k docker

Load the rules and list them to confirm:

sudo augenrules --load
sudo auditctl -l | grep docker
-w /usr/bin/dockerd -p rwxa -k docker
-w /usr/bin/containerd -p rwxa -k docker
...

Later you can search the events with sudo ausearch -k docker.

Step 5 - Hardening the Docker daemon

Most daemon warnings are fixed in /etc/docker/daemon.json. Open the file, creating it if it does not exist:

sudo nano /etc/docker/daemon.json

If the file already has settings, merge these keys into the existing JSON object instead of replacing it:

{
  "icc": false,
  "live-restore": true,
  "no-new-privileges": true,
  "userland-proxy": false,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

What each key does:

  • icc: false: blocks traffic between containers on the default bridge network. Containers on user-defined networks, which is what Compose creates, can still talk to each other.
  • live-restore: true: keeps containers running while the daemon restarts or is upgraded. It is not compatible with Swarm mode, so leave it out on Swarm nodes.
  • no-new-privileges: true: prevents processes in containers from gaining privileges through setuid binaries.
  • userland-proxy: false: uses iptables rules instead of a docker-proxy process for published ports.
  • log-opts: rotates container logs so they cannot fill the disk.

Validate the file and restart Docker:

sudo dockerd --validate --config-file /etc/docker/daemon.json
sudo systemctl restart docker
configuration OK

If systemctl restart fails, run sudo journalctl -u docker -n 50 to see which key the daemon rejected. Existing containers must be recreated to pick up no-new-privileges and the new log options.

Step 6 - Running containers with safer defaults

The container_runtime section checks every running container. Most warnings map to a docker run flag. Compare a default container with a hardened one that runs the same Nginx image:

docker run -d --name web-hardened \
  --read-only \
  --tmpfs /var/cache/nginx \
  --tmpfs /run \
  --tmpfs /tmp \
  --cap-drop ALL \
  --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --memory 256m --cpus 0.5 --pids-limit 100 \
  --restart on-failure:5 \
  --health-cmd "wget -q --spider http://127.0.0.1/ || exit 1" \
  -p 127.0.0.1:8080:80 \
  nginx:alpine

These flags answer the most common runtime warnings:

  • --read-only with --tmpfs for the few directories Nginx writes to: the container filesystem cannot be modified.
  • --cap-drop ALL plus a minimal --cap-add list: removes Linux capabilities the process does not need.
  • --memory, --cpus and --pids-limit: one container cannot exhaust the host.
  • --restart on-failure:5: limits restart loops.
  • --health-cmd: gives Docker a way to detect a broken container.
  • -p 127.0.0.1:8080:80: binds the port to a specific address instead of all interfaces.

Check that the container is healthy:

docker ps --filter name=web-hardened --format '{{.Names}}: {{.Status}}'
web-hardened: Up 40 seconds (healthy)

In Compose, the same settings are read_only, tmpfs, cap_drop, cap_add, security_opt, deploy.resources.limits, restart and healthcheck.

Step 7 - Measuring the improvement

Run the audit again and compare the score with the first run:

sudo sh docker-bench-security.sh
...
[INFO] Checks: 117
[INFO] Score: 21

The audit, daemon and runtime warnings you addressed now show PASS. Any remaining warnings should be in your "plan" or "accept" groups.

Step 8 - Scheduling a weekly audit

Running the audit regularly catches regressions, such as a new container started with --privileged. Copy the repository to a system location and create a systemd service:

sudo cp -r ~/docker-bench-security /opt/docker-bench-security
sudo mkdir -p /var/log/docker-bench
sudo nano /etc/systemd/system/docker-bench.service
[Unit]
Description=Docker Bench for Security audit
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
WorkingDirectory=/opt/docker-bench-security
ExecStart=/bin/sh docker-bench-security.sh -b -l /var/log/docker-bench/docker-bench.log

The -b flag disables colors in the output and -l sets the log file. Create a timer that runs it every Monday:

sudo nano /etc/systemd/system/docker-bench.timer
[Unit]
Description=Weekly Docker Bench for Security audit

[Timer]
OnCalendar=Mon *-*-* 06:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable the timer and trigger one run now to test it:

sudo systemctl daemon-reload
sudo systemctl enable --now docker-bench.timer
sudo systemctl start docker-bench.service

Check the result and the next scheduled run:

grep -c WARN /var/log/docker-bench/docker-bench.log
systemctl list-timers docker-bench.timer
18
NEXT                        LEFT     LAST                        PASSED  UNIT               ACTIVATES
Mon 2026-09-28 06:00:00 UTC 2 days   Fri 2026-09-25 10:32:11 UTC 1min ago docker-bench.timer docker-bench.service

Each run overwrites the log, and docker-bench.log.json next to it can be fed into your monitoring system to alert when the number of warnings rises. Remember to git pull in /opt/docker-bench-security from time to time to get new checks.

Conclusion

You audited your Docker host against the CIS Docker Benchmark, added audit rules, hardened the daemon configuration, started containers with restrictive runtime options and scheduled a weekly audit. Next, consider scanning your images for known vulnerabilities with a tool such as Trivy, enabling user namespace remapping on a test host, and putting a host intrusion prevention system such as CrowdSec in front of the services you publish.