Docker Container Logs and Rotation
Container logs are files on the host and by default they grow without limit. Measure one at 3.3 MB, cap it at 36 KB, and see exactly what rotation costs you in retained history.
Operations and Troubleshooting Guide 31 of 46 Intermediate
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- cgroupv2, systemd driver
- Architectureamd64
- TimeAbout 12 min
- Reviewed21 August 2026
Tested on the versions above. Log paths are host-specific and require root to read directly. Sizes vary with the workload.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- Reading logs with the CLI - guide 7 in this path.
- Root access on the host for the file-level steps.
-
Logs are files, and you own the disk they sit on
The default
json-filedriver writes every line a container sends to stdout or stderr into a JSON file under/var/lib/docker/containers/./ docker logsreads that file. Nothing rotates it by default, which is how a chatty container quietly fills a disk weeks after deployment.bash Example session docker info --format "default logging driver: {{.LoggingDriver}}"default logging driver: json-filedocker inspect cg-nolog --format "{{.LogPath}}"/var/lib/docker/containers/06f643c8b4b7.../06f643c8b4b7...-json.logExpected resultA path under /var/lib/docker. The file is owned by root and readable only by root.
Success conditionYou can name the file behind
docker logs. That file is what fills the disk. -
Measure an unbounded log
Thirty thousand short lines is a trivial amount of output - a busy service produces that in minutes. Here it is 3.3 MB, and nothing will ever remove it except deleting the container.
bash Example session docker run -d --name cg-nolog alpine:3.22 sh -c "yes cg-log-line-padding-padding-padding-padding | head -30000; sleep 90"docker logs cg-nolog | wc -l30000sudo du -h --apparent-size $(docker inspect cg-nolog --format {{.LogPath}}) | cut -f13.3MExpected resultEvery line retained, and megabytes on disk.
Success conditionYou have a number. Multiply it by your real log rate and your uptime - that is the disk this container will consume.
-
Cap it per container
--log-opt max-sizerotates the file at a size, andmax-filekeeps that many rotations. The values are per container, so you can apply them to one noisy service without touching the daemon or restarting anything else.bash docker run -d --name cg-rot --log-opt max-size=10k --log-opt max-file=3 alpine:3.22 sh -c "yes cg-log-line-padding-padding-padding-padding | head -30000; sleep 90"docker inspect cg-rot --format "opts={{.HostConfig.LogConfig.Config}}"opts=map[max-file:3 max-size:10k]Expected resultThe options recorded on the container.
Success conditionThe options are on the container, not just in your command line. A container created before you set them keeps the old behaviour until it is recreated.
-
See what rotation costs you
This is the trade nobody mentions. Disk is now bounded - three files, 36 KB total instead of 3.3 MB. But
docker logscan only return what is still on disk, so 29,744 of your 30,000 lines are simply gone. Rotation is not archiving.bash Example session sudo sh -c "ls /var/lib/docker/containers/$(docker inspect cg-rot --format {{.Id}})/ | grep json.log"6037d1fe1b47...-json.log6037d1fe1b47...-json.log.16037d1fe1b47...-json.log.2sudo sh -c "du -ch /var/lib/docker/containers/$(docker inspect cg-rot --format {{.Id}})/*json.log* | tail -1"36K totalExpected resultThree files totalling 36 KB, and only 256 lines retrievable.
Verify it worked
bash Example session docker logs cg-rot | wc -l256docker logs cg-nolog | wc -l30000Success conditionYou can state the trade in one sentence: bounded disk, lost history. If you need the history, ship it somewhere before it rotates.
-
Set the default for every new container
Per-container options do not help with the containers you forget. The durable fix is a daemon default in
/etc/docker/daemon.json, which applies to every container created afterwards. Understand the blast radius first: editing that file and reloading affects the whole host, and a malformed file stops the daemon starting at all.bash # host-wide change - validate the JSON and understand that a bad file breaks the daemon{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }}docker info --format "{{.LoggingDriver}}"# existing containers keep their old settings - only new ones inherit the defaultExpected resultThe current driver. This step is deliberately not executed here - it changes a shared host.
Success conditionYou know where the default lives and that it is not retroactive. Recreate existing containers to pick it up.
-
Other drivers, and when to leave json-file
docker logsonly works withjson-file,localandjournald. Point a container atsyslog,fluentdor a cloud driver anddocker logsstops returning anything - which is correct behaviour, not a fault, because the logs now live elsewhere.localis worth knowing: it is more compact than json-file and rotates by default.bash Example session docker info --format "{{json .Plugins.Log}}"["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","splunk","syslog"]Expected resultThe drivers this daemon supports.
Success conditionYou can list your options. Pick a shipping driver for production and keep json-file with rotation for local work.
-
Clean up
Removing the container removes its log files with it. That is also the only way to reclaim the space of an unbounded log short of truncating the file by hand.
bash docker rm -f cg-nolog cg-rotcg-nologcg-rotExpected resultBoth names echoed back.
Success condition
docker ps -ais clean and the log files are gone with the containers.
Troubleshooting
docker logs returns nothing for a running container
Why: Either the application writes to a file inside the container rather than to stdout, or the container uses a driver that does not support reading back.
Fix:Containerised applications should log to stdout and stderr. Check the driver before assuming the application is silent.
bash docker inspect NAME --format "{{.HostConfig.LogConfig.Type}}"The disk filled up and /var/lib/docker is the culprit
Why: Usually unbounded container logs, image layers, or build cache. Logs are the one that grows silently forever.
Fix:Find the largest log files first, then set rotation so it cannot recur. See guide 32 for the full accounting.
bash sudo du -sh /var/lib/docker/containers/* | sort -h | tail -5Rotation is configured but the file is still huge
Why: The setting was added after the container was created. Log options are fixed at creation time.
Fix:Recreate the container.
docker updatecannot change logging options.bash docker inspect NAME --format "{{.HostConfig.LogConfig.Config}}"# map[] means no options were applied at creationLogs are missing after a restart
Why: Rotation discarded them, or the container was recreated rather than restarted - a new container starts a new empty log.
Fix:Ship logs off the host if you need them to outlive the container. Rotation bounds disk; it does not preserve anything.
bash docker inspect NAME --format "created={{.Created}} started={{.State.StartedAt}}"