Docker Desktop 4.94.0 came out on October 5. One line in its release notes changes where your container logs go: "Changed the default logging driver for new Linux containers to local to enable automatic log rotation and reduce disk usage." Docker Engine, which runs on your servers, did not change. It still writes json-file logs, and by default it never rotates them.
So your laptop and your servers now store logs in two different formats. Most of the time you will not notice, because docker logs reads both. You will notice if something reads the log files directly, such as a collector that tails /var/lib/docker/containers/*/*.log. We wrote the same log lines through both drivers on Docker Engine 29.9.0 and recorded what each one stores, what docker logs gives back, and what a file-tailing collector reads.
TLDR
- Docker Desktop 4.94 uses
localfor new Linux containers. Docker Engine still defaults tojson-filewithmax-sizeunlimited, and Docker's docs say that is only for backward compatibility. - 500,000 log lines (47.9 MB of text) took 83.8 MB with
json-fileand no options.localkept all 500,000 lines in 24.1 MB.json-filecapped at 10 MB x 3 used 23.8 MB, but kept only 142,146 lines. -
docker logsreturned all 500,000 lines from the uncappedjson-filecontainer and from thelocalone. - Fluent Bit's
tailinput, set up the usual way for a Docker host, read 500,000 records from thejson-filecontainer and 0 from thelocalone. Pointed at thelocalfile directly, it read binary framing that the docker parser could not parse. - A changed default only reaches containers created after the change. Existing containers keep
json-fileuntil you recreate them.
Prerequisites
- Docker Engine or Docker Desktop, and
docker infoaccess -
sudoon a Linux Docker host, if you want to look at the log files yourself - Optional: the demo repo, which runs every test in this post
What changed, and what did not
Docker has always written container output to files on the host, in a format chosen by the log driver. The default has been json-file since the early days: one JSON object per line, with the text, the stream and a timestamp. Its documentation gives max-size a default of -1, which means unlimited, so one noisy container can fill the disk. The logging overview says Docker keeps json-file without rotation as the default "to remain backwards compatible with older versions of Docker", and recommends local for other cases because "it performs log-rotation by default, and uses a more efficient file format."
The local driver keeps 5 files of 20 MB each per container, so 100 MB of log text, and compresses the files it rotates out. Docker Desktop 4.94 now uses it for new Linux containers. Docker Engine 29.9.0, the newest Engine release when we tested, still reports json-file:
Docker Engine 29.9.0 on Linux
$ docker version --format 'docker client {{.Client.Version}}, engine {{.Server.Version}}'
docker client 29.9.0, engine 29.9.0
$ docker info --format '{{.LoggingDriver}}'
json-file
We could not run Docker Desktop on our test machine. The Desktop behaviour in this post comes from its release notes. Every measurement comes from Docker Engine 29.9.0 with the same two drivers. The output above is from a daemon with no logging settings (runs/.../00-default.txt).
The test
The demo repo starts three containers that each print the same 500,000 lines, about 96 bytes each, shaped like access logs:
-
logs-json-default:json-filewith no options, which is what Docker Engine does out of the box -
logs-json-capped:json-filewithmax-size=10mandmax-file=3, a common hand-made fix -
logs-local:localwith its defaults, which is what Docker Desktop 4.94 now does
Then it measures the files each driver wrote, counts what docker logs returns, and runs a real collector against the files. We ran it on a Raspberry Pi 4 (arm64) with Docker Engine 29.9.0 and Fluent Bit 5.1.3. The outputs below come from runs/2026-10-09-engine-29.9.0/ in the repo.
Same 500,000 lines, three setups (excerpt)
$ LINES=500000 ./scripts/01-write-logs.sh
logs-json-default: driver=json-file options={}
logs-json-capped: driver=json-file options={"max-file":"3","max-size":"10m"}
logs-local: driver=local options={}
$ ./scripts/02-measure.sh
container driver file_bytes files lines_kept bytes_of_text
logs-json-default json-file 83833258 1 500000 47888900
logs-json-capped json-file 23833155 3 142146 13614435
logs-local local 24134700 3 500000 47888900
# the file listings and byte dumps that follow are in 02-measure.txt
Size of the log files for the same 500,000 lines
| Value | Series | |
|---|---|---|
| json-file, no options | 83.8MB | json-file |
| json-file, 10m x 3 | 23.8MB | json-file |
| local, defaults | 24.1MB | local |
Docker Engine 29.9.0, 500,000 lines of about 96 bytes (47.9 MB of text). json-file with no options kept all 500,000 lines, json-file capped at 10 MB x 3 kept 142,146, local kept all 500,000. These lines repeat a lot; compression depends on your log content.
Three things stand out:
-
json-filecosts more than the text. 47.9 MB of text took 83.8 MB of files, 1.75 times the text, because every line is wrapped in{"log":...,"stream":...,"time":...}. That is about 72 extra bytes per line here. With nomax-size, the file only grows. -
Our size cap on
json-filethrew data away. The capped container used 23.8 MB anddocker logsreturned only 142,146 of the 500,000 lines. The rest rotated out. -
localkept everything in about the same space. It used 24.1 MB and returned all 500,000 lines. The current file was 19.4 MB, uncompressed, framing included. Each of the two rotated files was about 2.3 MB after gzip, and the driver rotates a file at 20 MB before compression.
The two capped setups do not have the same budget. json-file at 10 MB x 3 allows 30 MB. local allows 5 files of 20 MB each before compression, framing included, and it also drops its oldest lines when it reaches that limit. A container that writes more than about 100 MB of log records loses lines with either driver. These lines repeat a lot, so they compressed well; how well your logs compress depends on their content.
The file format is what changes
docker logs returned all 500,000 lines from both uncapped containers, because it reads through the Docker daemon. The files on disk are a different story. Here are the first bytes of each file, from the same run:
First bytes of each driver's file, from 02-measure.txt (first 3 of 6 od lines)
# json-file: <container-id>/<container-id>-json.log
0000000 { " l o g " : " 2 0 2 6 - 1 0 -
0000020 0 9 T 1 0 : 0 0 : 0 0 . 0 0 0 0
0000040 0 1 Z l e v e l = i n f o m
# local: <container-id>/local-logs/container.log
0000000 \0 \0 \0 s \n 006 s t d o u t 020 205 330 232
0000020 342 244 221 265 356 030 032 _ 2 0 2 6 - 1 0 -
0000040 0 9 T 1 0 : 0 0 : 0 0 . 3 4 1 6
json-file writes one JSON object per line, in a file named <id>-json.log in the container's directory. local writes length-prefixed binary records in a local-logs/ subdirectory, named container.log, and the rotated files end in .gz. Docker's docs say these files "are designed to be exclusively accessed by the Docker daemon", and that reading them with external tools "should be avoided".
What a file-tailing collector sees
Many Docker hosts ship logs with an agent that tails the files. We ran Fluent Bit 5.1.3 with its tail input and the docker parser on each container's *.log files, the usual setup for a Docker host:
Fluent Bit tail input, docker parser (excerpt)
$ ./scripts/03-collector.sh
Fluent Bit: Fluent Bit v5.1.3
== the usual Docker host pattern: <container dir>/*.log, docker parser
logs-json-default records read: 500000
logs-json-capped records read: 22860
logs-local records read: 0
== aimed at the local driver's own file: local-logs/container.log
records read: 158382
records the docker parser could parse: 0
# two sample records follow; the first 20 are in 03-local-raw-head.txt
The json-file container gave all 500,000 records. For the local container the counter reported 0, because *.log in the container directory does not match local-logs/container.log. When we pointed Fluent Bit at that file directly, it read 158,382 records and the parser understood none of them. The tail input splits on newline bytes, which this binary format also uses inside its framing, so the records are fragments: the first one holds only a length byte, and the others hold one message each with binary framing around it, in the form ^Fstdout^P... (shown with cat -v). It also left out the two rotated .gz files.
The capped container's 22,860 is a side effect of the test. Fluent Bit started after the container had finished, so it found only the current -json.log file. A collector that runs all the time can read lines before they rotate, but this run did not test that.
What this means for other collectors depends on how they read:
-
Collectors that read through the Docker daemon should keep working, because the daemon reads the
localformat for them, as it does fordocker logs. Grafana Alloy'sloki.source.docker"reads log entries from Docker containers" through the daemon address you give it. Vector'sdocker_logssource connects todocker_host. We did not test either of them withlocal. -
Collectors that parse the files as Docker JSON cannot decode
local. Fluent Bit'stailinput with the docker parser is one, as measured above. Filebeat's container input reads files and parses thedockerandcriformats, and thelocalformat is neither. That input is deprecated, and Filebeat's docs say to use thefilestreaminput with itscontainerparser instead.
We tested Fluent Bit only. For the others, the docs above are our source, so test your own agent before you depend on this.
Existing containers keep their driver
The log driver is set when a container is created. Docker's docs say that changing the default "only affects containers that are created after the configuration is changed". We checked it: we created a container, restarted the daemon with local as the default, then created a second one and restarted the first.
Changing the daemon default
$ ./scripts/04-default-change.sh before
== before: daemon default is json-file
logs-before: json-file
# restart dockerd with --log-driver local
$ ./scripts/04-default-change.sh after
== after: daemon default is local
logs-before (created before, started again now): json-file
logs-after (created after): local
We tested this on Engine, not on a Desktop upgrade, but the rule is the same. On a laptop that you upgrade to 4.94, the containers you already have keep the driver they were created with. A container that you remove and run again, or that Compose recreates, gets the new default, unless its configuration names a driver. For a while, one machine can have both formats side by side.
What to do
Check which driver each container actually has, not only the default:
# The daemon default
docker info --format '{{.LoggingDriver}}'
# The driver of every container, running or not
docker ps -aq | xargs docker inspect --format '{{.Name}} {{.HostConfig.LogConfig.Type}}'
On servers running Docker Engine , the risk is the old one: json-file with no limit. Pick a default in /etc/docker/daemon.json. Use local if your collectors read through the Docker API:
{
"log-driver": "local"
}
Or keep json-file for a file-tailing collector and give it a limit:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Restart the daemon, then recreate the containers so that they pick up the setting. As the measurements show, a size cap on json-file means the oldest lines are gone from disk. That is fine if a collector has already shipped them, and a loss if it has not.
On Docker Desktop , you probably need to do nothing. docker logs works with both drivers, and tools that use the same API should too. If you run a file-tailing collector on your laptop, set "log-driver": "json-file" in Docker Desktop's daemon settings, or move the collector to a source that reads through the Docker API. We could not see where 4.94 sets its new default, so run the docker info check above after the upgrade.
In Compose files , set the driver for each service if a service depends on it, so the laptop and the server do the same thing:
services:
api:
image: example/api:1.4
logging:
driver: json-file
options:
max-size: '10m'
max-file: '3'
To clear logs that already fill a disk, see How to Clear Docker Container Logs Properly.
The-DevOps-Daily/docker-log-driver-check on GitHub
Summary
Docker Desktop 4.94 moved new Linux containers to the local log driver. That is the driver Docker's own docs recommend, and in our test it kept all 500,000 lines in 24 MB of files where unbounded json-file used 84 MB. Docker Engine on servers still defaults to json-file with no rotation, so set a default on purpose. If something reads the files under /var/lib/docker/containers, it needs json-file. If it reads through the Docker API, local is the better default for both laptops and servers. In both cases, check docker inspect, because a new default does nothing for containers that already exist.
Originally published at devops-daily.com.
Top comments (0)