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
sudoprivileges. gitinstalled (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:
| Status | Meaning |
|---|---|
PASS | The host follows the recommendation. |
WARN | The recommendation is not met. Review it. |
INFO | Information to review manually, such as the list of users in the docker group. |
NOTE | A 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:
- Fix now: cheap changes with clear security value, such as audit rules, daemon options and container flags. This tutorial covers these.
- Plan: changes that need downtime or testing, such as moving
/var/lib/dockerto its own partition or enabling user namespace remapping. - 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 defaultbridgenetwork. 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 adocker-proxyprocess 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-onlywith--tmpfsfor the few directories Nginx writes to: the container filesystem cannot be modified.--cap-drop ALLplus a minimal--cap-addlist: removes Linux capabilities the process does not need.--memory,--cpusand--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.
