
Control Docker Log Growth with Rotation Limits
Control Docker Log Growth with Rotation Limits
A healthy container can quietly fill a server disk. Docker captures a container’s standard output and standard error, and the default json-file logging driver does not rotate logs unless you configure limits. A noisy debug statement, failing health check, or request loop can therefore consume gigabytes while the container itself appears healthy.
This tutorial audits the active logging driver, configures safe rotation globally or per Compose service, recreates existing containers correctly, and adds monitoring that catches growth before the filesystem reaches 100 percent.
The configuration follows the current Docker Engine and Compose documentation as reviewed in September 2026. Check your installed Engine with docker version; logging-driver behavior is set when each container is created.
Understand where the growth comes from
Applications should normally write operational logs to stdout and stderr. Docker’s logging driver receives those streams and decides where and how to store or forward them.
Docker Engine uses json-file by default for compatibility. Each line is stored with its stream and timestamp. Without max-size, the file’s default maximum is unlimited. Docker explicitly recommends the local driver for general non-Kubernetes use because it rotates logs by default and uses a more efficient internal format.
Log rotation is retention, not observability. Local files help docker logs work, but they are not a searchable, durable logging platform. Important production events should still be shipped to a central system with access control, backups, and alerting.
Audit the current configuration
Check the daemon’s default driver:
docker info --format '{{.LoggingDriver}}'
Inspect a specific container:
docker inspect --format \
'{{.Name}} driver={{.HostConfig.LogConfig.Type}} options={{json .HostConfig.LogConfig.Config}}' \
my-api
An empty driver in the inspection output means the container inherited the daemon default when it was created. It does not mean that logging is disabled.
Review disk pressure at the filesystem level:
df -h /var/lib/docker
sudo du -xhd1 /var/lib/docker | sort -h
sudo du -xhd2 /var/lib/docker/containers | sort -h | tail
The Docker data root may differ, especially with rootless Docker or a custom data-root. Find it with:
docker info --format '{{.DockerRootDir}}'
Use filesystem commands only to measure Docker-managed log files. Docker warns against editing, moving, or deleting logging-driver files with external tools because doing so can interfere with the daemon.
Choose between local and rotated json-file
For most standalone Docker Engine hosts, choose local:
- automatic rotation is enabled by default;
- the internal format is optimized for performance and disk use;
- rotated logs are compressed by default;
docker logscontinues to work.
The documented defaults preserve about 100 MB per container: five files with a 20 MB size limit. Make the values explicit so operators do not need to remember defaults.
Use json-file when another tool requires its JSON format or when platform compatibility demands it. In that case, configure both max-size and max-file; max-file only has an effect when max-size is set.
Configure the daemon-wide default
On a typical Linux installation, Docker Engine reads /etc/docker/daemon.json. Inspect the existing file before changing it:
sudo test -f /etc/docker/daemon.json \
&& sudo cat /etc/docker/daemon.json \
|| echo 'No daemon.json exists yet'
Do not overwrite unrelated settings such as registry mirrors, address pools, runtimes, or the data root. Merge the logging keys into the existing JSON object.
For the local driver:
{
"log-driver": "local",
"log-opts": {
"max-size": "20m",
"max-file": "5",
"compress": "true"
}
}
For rotated json-file:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5",
"compress": "true"
}
}
Docker requires daemon.json logging option values to be strings. Quote numbers and booleans exactly as shown.
Validate the JSON syntax before restarting the daemon:
sudo jq empty /etc/docker/daemon.json
If jq is unavailable, use another real JSON parser. A visual inspection can miss a trailing comma or duplicated structural brace.
Plan the restart during an appropriate maintenance window:
sudo systemctl restart docker
sudo systemctl --no-pager --full status docker
docker info --format '{{.LoggingDriver}}'
Depending on daemon and host configuration, restarting Docker can interrupt containers. Verify your live-restore configuration and service requirements rather than assuming there is no impact.
Most importantly, changing the daemon default affects only containers created afterward. Existing containers retain the driver and options captured at creation time.
Set rotation per Docker Compose service
Per-service configuration travels with the application and avoids relying on every host’s daemon default.
services:
api:
image: ghcr.io/example/api:1.8.0
restart: unless-stopped
logging:
driver: local
options:
max-size: "20m"
max-file: "5"
compress: "true"
worker:
image: ghcr.io/example/worker:1.8.0
restart: unless-stopped
logging:
driver: local
options:
max-size: "50m"
max-file: "3"
compress: "true"
Validate the rendered configuration:
docker compose config --quiet
docker compose config
Then recreate one service at a time:
docker compose up -d --no-deps --force-recreate api
docker inspect --format \
'{{.HostConfig.LogConfig.Type}} {{json .HostConfig.LogConfig.Config}}' \
"$(docker compose ps -q api)"
Recreation replaces the container but preserves named volumes. Bind mounts and external services behave according to the Compose file. Check health, migrations, and load-balancer behavior before proceeding to the next service.
If all services inherit the daemon default, recreating them is still required after changing the default:
docker compose up -d --force-recreate
That command may restart multiple services together. On a production stack, use a rollout plan that maintains capacity.
Estimate a sensible retention budget
A rough uncompressed ceiling is:
containers × max-size × max-file
For 20 containers at 20 MB across five files, the nominal ceiling is about 2 GB. Compression can reduce the physical usage for rotated logs, but the ratio depends on the content. Leave room for images, writable layers, volumes, build cache, database files, and temporary decompression overhead.
Choose retention from operational needs:
- How many hours of local history are useful during an outage?
- How fast does the noisiest container log during an incident?
- Are logs shipped centrally before local rotation deletes them?
- How much disk must remain free for deployments and database growth?
A very small cap can erase the evidence you need. A very large cap only postpones disk exhaustion. Measure peak log rate and set an alert well below the theoretical ceiling.
Test rotation with a disposable container
Create a test container with deliberately small limits:
docker run --name log-rotation-test --rm \
--log-driver local \
--log-opt max-size=1m \
--log-opt max-file=2 \
alpine:3.22 \
sh -c 'i=0; while [ "$i" -lt 200000 ]; do echo "test-line-$i"; i=$((i+1)); done'
While it runs, inspect the configuration and confirm logs remain readable:
docker inspect --format \
'{{.HostConfig.LogConfig.Type}} {{json .HostConfig.LogConfig.Config}}' \
log-rotation-test
docker logs --tail 20 log-rotation-test
Because --rm removes the stopped test container, run inspection while it is active or omit --rm temporarily. Do not use a production container to generate artificial noise.
Decide whether logging may block the application
Docker’s default delivery mode is blocking: the application writes to stdout or stderr, and logging back pressure can eventually slow or block it. Docker also supports non-blocking delivery through an in-memory buffer:
services:
telemetry-producer:
image: ghcr.io/example/telemetry-producer:2.1.0
logging:
driver: local
options:
max-size: "20m"
max-file: "5"
mode: non-blocking
max-buffer-size: "4m"
Non-blocking mode protects application throughput, but Docker drops new messages when the buffer is full. That tradeoff may be acceptable for verbose debug telemetry and unacceptable for audit events. Make the choice per workload, monitor dropped or missing logs indirectly, and never describe non-blocking delivery as lossless.
Monitor disk and log behavior
At minimum, alert on filesystem utilization and inode usage:
df -h "$(docker info --format '{{.DockerRootDir}}')"
df -i "$(docker info --format '{{.DockerRootDir}}')"
Periodically inventory container policies:
docker ps -q | xargs -r docker inspect --format \
'{{.Name}} {{.HostConfig.LogConfig.Type}} {{json .HostConfig.LogConfig.Config}}'
Treat an empty type as inherited configuration and compare it with the daemon default. Include logging policy in deployment checks so a new Compose service cannot silently return to unlimited json-file logs.
Application logging quality matters as much as rotation. Avoid logging secrets, access tokens, full request bodies, or personal data. Use structured messages, severity levels, request IDs, and sampling for noisy success paths.
Common failures and fixes
Docker will not restart after editing daemon.json
Check JSON syntax and daemon logs:
sudo jq empty /etc/docker/daemon.json
sudo journalctl -u docker.service -n 100 --no-pager
Look for a duplicate option also supplied as a dockerd command-line flag. Restore the last known-good file instead of repeatedly guessing on a production host.
The new policy does not appear on a container
The container predates the configuration change. Recreate it and inspect .HostConfig.LogConfig. Restarting the existing container is not the same as recreating it.
max-file does not rotate json-file logs
Set max-size too. Docker documents that max-file is effective only when a size limit is configured.
Disk use stays high after rotation
Confirm which directory is consuming space. Images, stopped containers, writable layers, volumes, database files, and build cache are separate from logs. Also allow for temporary space when compressed logs are read.
docker logs is slow for a large time range
Request a bounded slice:
docker logs --since 15m --tail 1000 my-api
Reading compressed rotated logs can temporarily increase CPU and disk use. Use a central log system for broad historical searches.
Someone manually truncated Docker’s log file
Stop doing direct file manipulation. Recreate the affected container with a supported rotation policy and verify daemon health. External changes to driver-owned files can produce unexpected behavior.
Production checklist
- Check the daemon default with
docker info. - Inspect every important container’s effective log configuration.
- Prefer
localfor standalone Engine hosts unless compatibility requires another driver. - Set explicit
max-sizeandmax-filevalues. - Keep all
daemon.jsonlog option values quoted as strings. - Merge configuration instead of overwriting unrelated daemon settings.
- Validate JSON and plan the Docker daemon restart.
- Recreate existing containers to apply new options.
- Estimate the fleet-wide retention budget.
- Alert on filesystem space and inodes well before exhaustion.
- Never edit Docker-managed log files directly.
- Ship important logs centrally and keep secrets out of output.
- Document the loss tradeoff before enabling non-blocking mode.
- Verify the policy after every deployment.