Docker audit fix

How to Configure Docker Container Log Limits on VPS

Docker's default json-file log driver applies no size limit, so container logs grow until the disk is full and every service on the host stops. Cap them with log-opts max-size and max-file, or switch to the local driver, which rotates on its own.

Hiren KalariyaLast reviewed: Oct 6, 2026Check container-log-limits
Low
severity
No
needs root or sudo
4
results it can return
8 of 8
checks in this audit

Every threshold on this page is transcribed from the docker-security-daemon-socket audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Daemon & Socket checks, or how the audit runs.

Low severityNo sudo needed

What this check reads

It reads /etc/docker/daemon.json and greps two keys out of it, "log-driver" and "max-size". The driver decides the branch, and an absent "log-driver" is treated as json-file, which is Docker's own default. On json-file the presence of a "max-size" value anywhere in the file is the whole test: with one the check PASSes, without one it WARNs. Any other driver value PASSes outright, on the assumption that the driver handles its own rotation.

When it applies

Runs whenever the docker binary exists; no daemon access or root needed, since it only reads /etc/docker/daemon.json. It inspects the daemon-wide default log driver and max-size. With the default json-file driver and no max-size it WARNs; with any other driver it PASSes on the assumption that driver handles its own retention.

Known accuracy issue: per-container settings are invisible. A host that caps logs on every container with --log-opt max-size=10m (or a Compose logging: block) still gets a WARN, and conversely a daemon default of max-size does not prove a container has not overridden it. This is the known issue flagged at the top of this page.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
SKIPDocker not installedNothing was checked. With no docker binary on the host, no log driver setting was read at all.
PASS`json-file` with `max-size` setThe daemon default caps container logs and rotates them, so they cannot grow without bound.
WARN`json-file` without `max-size`The daemon default puts no ceiling on container logs, so one chatty container can fill the disk. Per-container caps are invisible to this check.
PASSOther driver (`local`, `journald`, `syslog`, ...)Logging is handed to a different driver and the check assumes that driver enforces its own retention. Confirm that on the receiving end.

Why it matters

Docker's default json-file driver has no size limit. A chatty or compromised container fills the disk, which stops the daemon, databases and SSH logins on the host (availability, and a denial-of-service vector). Docker docs recommend the local driver (which rotates by default) or explicit max-size/max-file. CIS Docker 2.13 covers centralised logging.

Why it fails, and when it is wrong

  • Per-container --log-opt max-size=10m (Compose logging: options:) is not read; hosts that cap logs per service get a false WARN. Confirm with docker inspect -f '{{.HostConfig.LogConfig}}' <name>.
  • Changing daemon.json only affects new containers; existing ones keep their original LogConfig until recreated.
  • daemon.json unreadable → both greps are empty → WARN even if configured.

How to fix it

Set a capped default in daemon.json

Merge this into the keys already in /etc/docker/daemon.json. The local driver rotates on its own; to keep json-file, use the same log-opts with "log-driver": "json-file". Both values must be strings.

{
  "log-driver": "local",
  "log-opts": { "max-size": "20m", "max-file": "5" }
}

Validate and restart Docker

An invalid daemon.json stops the daemon from starting, so check it parses first.

python3 -m json.tool /etc/docker/daemon.json >/dev/null && sudo systemctl restart docker

Recreate the containers

Existing containers keep the log configuration they were created with, so the new default only applies once they are recreated. Shipping logs to journald, syslog or Loki instead is the other route.

docker compose up -d --force-recreate

Verify the fix

# The daemon default (what the check reads):
sudo cat /etc/docker/daemon.json
docker info --format 'driver={{.LoggingDriver}}'

# What each running container actually uses - the part the check cannot see:
docker inspect -f '{{.Name}} {{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}' $(docker ps -q)
# expected: a max-size on every json-file container

# And the ground truth - how big the log files actually are:
# (sh -c, because the directory is root-only and the glob must expand as root)
sudo sh -c 'du -sh /var/lib/docker/containers/*/*-json.log' | sort -h | tail -5

Debugging

The known false positive above. Confirm with the docker inspect command; if every container carries a max-size, record it. Setting the daemon-wide default as well is the cleanest resolution, and it protects containers created later that forget the flag.

The check greps for "max-size" anywhere in the file, so a syntax problem is the likely cause. max-size belongs inside log-opts, and its value must be a string ("10m", not 10m). Validate with python3 -m json.tool /etc/docker/daemon.json; an invalid daemon.json also stops the daemon from starting.

Log configuration is fixed at container creation. Recreate them (docker compose up -d --force-recreate) for the new default to apply.

The check assumes journald, syslog, local and remote drivers manage their own retention. Verify that assumption on the receiving end: journald obeys SystemMaxUse in journald.conf, and a remote collector has its own policy. The local driver does rotate by default; json-file does not.

Truncate safely with sudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log' (deleting the file leaves the daemon writing to a removed inode), then fix the default and recreate the containers. The sh -c matters: the containers directory is readable only by root, so a plain sudo truncate with the glob fails because your own shell cannot expand it.

Sources

How the script reads this

Next

Re-run the Daemon & Socket audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Daemon & Socket fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals