15.1.2.1 Json File Driver
A focused guide to Json File Driver, connecting core concepts with practical Docker and container operations.
The json-file driver is Docker's default logging driver, storing each line a container writes to stdout or stderr as an individual JSON object in a plain text file on the host's local disk, and understanding its exact on-disk format and behavior clarifies both why it is simple to inspect directly and why it requires deliberate configuration to use safely in production.
Locating the log file on disk
Every container using the json-file driver has its log file stored at a predictable path under Docker's data root, named after the container's full ID:
docker inspect --format='{{.LogPath}}' my-api
/var/lib/docker/containers/3f29a8c1d8e2.../3f29a8c1d8e2...-json.log
This file can be read directly with standard Unix tools, entirely independently of the docker logs command, which is useful for low-level debugging or for a log collection agent that reads container log files directly from the host filesystem rather than going through the Docker API.
tail -f /var/lib/docker/containers/3f29a8c1d8e2.../3f29a8c1d8e2...-json.log
The on-disk line format
Each line in the file is a self-contained JSON object capturing the log message, which stream it came from, and a timestamp:
{"log":"Server started on port 3000\n","stream":"stdout","time":"2024-06-01T14:32:01.123456789Z"}
{"log":"Database connection failed\n","stream":"stderr","time":"2024-06-01T14:32:02.456789012Z"}
The log field contains the raw text the application wrote, including its own trailing newline; the stream field preserves the distinction between stdout and stderr even though Docker captures both into the same file; and the time field is a nanosecond-precision timestamp recorded by Docker at the moment it captured the line, not necessarily the exact moment the application produced it, though in practice the difference is negligible.
How docker logs reconstructs output from this format
docker logs parses this JSON format back into the plain text stream an operator expects to see, optionally adding timestamps or filtering by stream when requested:
docker logs --timestamps my-api
docker logs my-api 2>/dev/null
This is also why json-file is one of the drivers that supports docker logs at all: the driver retains a structured, parseable local copy that the command can read back and reformat, unlike drivers that only forward output to a remote destination without retaining anything queryable locally.
Size overhead compared to raw output
Because every line is wrapped in a JSON object with field names and a timestamp, the json-file format adds meaningful overhead compared to the raw text an application originally wrote, particularly for short, frequent log lines where the JSON wrapper can be a significant fraction of the total stored size:
echo -n '{"log":"ok\n","stream":"stdout","time":"2024-06-01T14:32:01.123456789Z"}' | wc -c
This overhead is part of why Docker's local driver, which uses a more compact binary format instead of JSON text, is generally recommended over json-file specifically when minimizing local disk usage matters more than having a human-readable raw file.
Compression option
The json-file driver supports compressing rotated log files, reducing the disk footprint of historical, no-longer-actively-written files while leaving the current, active log file uncompressed for ongoing writes:
docker run -d --log-opt max-size=10m --log-opt max-file=5 --log-opt compress=true my-api
Compressed rotated files are not directly readable with plain text tools without first decompressing them, which is a reasonable trade-off for historical files that are rarely accessed compared to the actively growing current log file.
Including labels and environment variables in log output
The json-file driver can be configured to attach selected container labels or environment variables to every log entry, which is useful for downstream collectors that want this metadata without needing to separately query the Docker API for it:
docker run -d --log-opt labels=service,version --log-opt env=NODE_ENV my-api
{"log":"Server started\n","stream":"stdout","time":"...","attrs":{"service":"my-api","version":"1.4.0","NODE_ENV":"production"}}
This embeds the specified metadata directly into each log line's attrs field, which can simplify a collector's enrichment step since the relevant context is already present in the log line itself rather than needing to be looked up separately.
Performance characteristics under high log volume
Because every write involves JSON-encoding the message along with its metadata before writing to disk, the json-file driver has measurably more CPU and I/O overhead per log line than a driver using a more compact format or forwarding output without local persistence at all. For most applications this overhead is negligible, but for an extremely high-throughput logging workload, it is worth being aware of as a potential, if usually minor, contributor to overall resource usage.
docker stats my-api
Comparing CPU usage of a high-volume container before and after a significant change in log verbosity is a reasonable way to confirm whether logging overhead is a meaningful factor for a specific workload, though for the vast majority of applications this is not a practical concern.
When json-file is and is not the right choice
json-file remains a reasonable default for simple, single-host, low-to-moderate volume deployments where the simplicity of a directly readable text file and full docker logs compatibility outweigh its overhead compared to more compact or remote-native alternatives. For higher-volume or multi-host production deployments, the local driver (for reduced overhead while keeping local readability) or a remote-native driver (for centralized aggregation without relying on local disk at all) are generally better choices.
Common mistakes
- Running json-file with no
max-sizeormax-filelimits configured, allowing unbounded growth on any long-running, even moderately verbose container. - Assuming the raw on-disk file format is the same as what
docker logsdisplays, when it is actually a structured JSON wrapper that needs to be parsed back into plain text. - Choosing json-file by default for a high-volume production workload without considering whether the
localdriver's more compact format would reduce overhead with no loss of functionality. - Forgetting that compressed rotated log files are not directly readable as plain text without decompression first.
- Reading container log files directly from disk in scripts without accounting for the JSON wrapper format, leading to broken parsing when the raw file is treated as plain text.
The json-file driver's value comes from its simplicity, a directly inspectable, human-readable file on disk, full docker logs compatibility, and straightforward configuration, but that simplicity comes with real overhead and an unbounded-by-default growth risk that needs to be explicitly addressed through rotation and compression settings before relying on it in production.