JSON in Docker: daemon.json, Compose Labels & JSON Logging

Docker uses JSON throughout its configuration and output — from the daemon configuration file to container inspection data and the default logging driver. The Docker daemon reads /etc/docker/daemon.json for global settings: default logging driver, registry mirrors, DNS servers, and resource limits. The json-file logging driver (Docker's default) writes container logs as JSON objects to disk at /var/lib/docker/containers/<id>/<id>-json.log, with each log line a JSON object containing log, stream, and time keys. docker inspect <container> returns a JSON array with the full container configuration — 200+ fields including network settings, mounts, environment variables, and health check status. The docker inspect --format flag accepts Go templates, but piping to jq is more flexible: docker inspect mycontainer | jq '.[0].NetworkSettings.IPAddress'. Docker Compose labels are string key-value pairs used by tools like Traefik for routing configuration — often expressed as JSON-style strings. This guide covers daemon.json, json-file logging, docker inspect with jq, environment variables as JSON, and Docker Compose JSON patterns.

Need to validate or pretty-print a JSON config like daemon.json? Jsonic's formatter catches syntax errors instantly.

Open JSON Formatter

daemon.json: Docker Daemon Configuration

/etc/docker/daemon.json configures the Docker daemon globally — it is the single file that controls default behavior for every container on the host. Key fields include "log-driver" (default "json-file"), "log-opts" with max-size and max-file for rotation, "registry-mirrors" (an array of mirror URLs), "dns" (an array of DNS server IPs), and "default-ulimits" for resource limits. The following example sets log rotation to 10 MB per file across 3 files (30 MB total per container) and adds a registry mirror:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "registry-mirrors": ["https://mirror.example.com"],
  "dns": ["8.8.8.8", "8.8.4.4"],
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 64000,
      "Soft": 64000
    }
  }
}

After editing daemon.json, restart the Docker daemon to apply changes: sudo systemctl restart docker on Linux. On macOS, restart Docker Desktop from the menu bar. Invalid JSON in daemon.json prevents Docker from starting entirely — validate the file with docker daemon --validate or paste it into Jsonic's JSON formatter before restarting. The daemon merges daemon.json with CLI flags, but any conflict between the file and a flag causes startup to fail with an error message in journalctl -u docker. Always keep a backup of a known-good daemon.json before making changes in production.

json-file Logging Driver

Docker's default json-file driver writes logs to /var/lib/docker/containers/<id>/<id>-json.log on the host. Each line in the file is a self-contained JSON object — this is the NDJSON log format:

{"log":"output text\n","stream":"stdout","time":"2026-05-13T00:00:00Z"}

The 3 fields are always present: log contains the raw output text (including the trailing newline character), stream is either stdout or stderr, and time is an RFC 3339 timestamp with nanosecond precision. You view logs via docker logs <container> — Docker parses the JSON internally and returns just the log field text. To limit disk usage, apply rotation options at 3 levels: globally in daemon.json, per-service in Docker Compose, or per-container at run time:

# Per-container: limit to 5 files of 10 MB each (50 MB total)
docker run \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=5 \
  myimage

For JSON structured logging, emit JSON objects to stdout from your application — for example {"level":"info","msg":"request","path":"/health","ms":2}. The json-file driver stores this as a JSON string inside the log field, resulting in double-wrapped JSON. Parse with: docker logs mycontainer | jq -R 'fromjson | .log | fromjson' to unwrap and filter the inner structured log.

docker inspect and jq

docker inspect <container> returns a 200+ field JSON array containing the complete container configuration and runtime state. The output covers network settings, mounted volumes, environment variables, health check configuration, restart policy, and much more. Pipe to jq for targeted field extraction — far more flexible than the --format Go template flag for complex queries:

# IP address of a running container
docker inspect mycontainer | jq '.[0].NetworkSettings.IPAddress'

# All environment variables (one per line)
docker inspect mycontainer | jq '.[0].Config.Env[]'

# All bind-mount source paths
docker inspect mycontainer | jq '.[0].Mounts[].Source'

# Container running state
docker inspect mycontainer | jq '.[0].State.Status'

# Exposed ports on an image
docker image inspect myimage | jq '.[0].Config.ExposedPorts'

# Multiple containers at once
docker inspect web db cache | jq '.[].NetworkSettings.IPAddress'

Always use .[0] when inspecting a single container — the output is always a JSON array even for 1 container. The --format Go template approach is simpler for single scalar values: docker inspect --format '{{.NetworkSettings.IPAddress}}' mycontainer. Docker 25+ adds docker ps --format json which outputs each container as a compact JSON object per line (NDJSON), enabling streaming jq pipelines: docker ps --format json | jq 'select(.Status | contains("Up")) | .Names'. For image layers and history use docker history --format json myimage | jq '.[].CreatedBy'.

Environment Variables as JSON

Container environment variables are always strings — but you can embed a JSON object as the string value and parse it inside the application. This lets a single env var carry structured configuration with 10+ fields rather than requiring one env var per setting. Pass JSON at run time with careful shell quoting:

# docker run — single quotes prevent shell from interpreting braces
docker run \
  -e CONFIG='{"timeout":30,"retries":3,"debug":false}' \
  myapp

# Docker Compose — YAML string syntax
services:
  app:
    image: myapp
    environment:
      CONFIG: '{"timeout":30,"retries":3}'

# --env-file — no outer quotes needed in env files
# .env file:
CONFIG={"timeout":30,"retries":3}

docker run --env-file .env myapp

Parse the value inside your application before use. In Node.js: const config = JSON.parse(process.env.CONFIG). In Python: config = json.loads(os.environ["CONFIG"]). Always wrap the parse in a try/catch — a malformed JSON env var will crash your app at startup rather than at the point of use. For production secrets (API keys, database passwords), prefer Docker Secrets (/run/secrets/<name>) or a secrets manager over env vars, as env vars are visible in docker inspect output and process listings. Use Jsonic's JSON formatter to validate the JSON string before embedding it in a compose file or Dockerfile.

Docker Compose JSON Labels

Docker labels are string key-value pairs attached to containers, images, networks, and volumes — they carry metadata consumed by orchestration tools like Traefik, Portainer, and Watchtower. In Docker Compose, labels are declared under the labels key either as a YAML mapping or as a list of strings. Traefik routing configuration — one of the most common label use cases — drives 100% of its configuration from container labels:

services:
  web:
    image: myapp
    labels:
      # Traefik routing
      traefik.enable: "true"
      traefik.http.routers.myapp.rule: "Host(`myapp.example.com`)"
      traefik.http.routers.myapp.entrypoints: "websecure"
      traefik.http.services.myapp.loadbalancer.server.port: "3000"

      # Custom reverse-DNS labels
      com.example.team: "backend"
      com.example.version: "2.1.0"

      # JSON value in a label (must escape braces)
      myapp.config: '{"timeout":30,"retries":3}'

For tools that accept JSON as a label value, the JSON must be escaped or single-quoted in YAML. Inspect labels at runtime with:

# All labels for a container
docker inspect mycontainer | jq '.[0].Config.Labels'

# A specific label value
docker inspect mycontainer | jq '.[0].Config.Labels["com.example.team"]'

# Filter containers by label
docker ps --filter label=com.example.team=backend

Use reverse-DNS notation for custom label keys (e.g. com.example.appname) to avoid collisions with tool-specific label namespaces. Label values that contain JSON should be validated before deployment — paste them into Jsonic's JSON formatter to catch missing quotes or trailing commas early. See also JSON API responses for patterns used with containerised API services.

Key terms

daemon.json
The JSON configuration file read by the Docker daemon at startup, located at /etc/docker/daemon.json on Linux, that controls global defaults such as the logging driver, registry mirrors, DNS servers, and resource limits.
json-file logging driver
Docker's default logging driver, which persists each container log line as a JSON object containing log, stream, and time fields to a file on the Docker host filesystem.
docker inspect
A Docker CLI command that returns detailed configuration and runtime state for one or more containers or images as a JSON array, covering 200+ fields including network settings, mounts, and environment variables.
log rotation
The automatic management of log file size by limiting each file to a maximum size (via max-size) and keeping only a fixed number of files (via max-file), preventing container logs from filling the host disk.
Docker labels
Arbitrary string key-value metadata attached to Docker objects (containers, images, networks, volumes) that external tools like Traefik and Portainer read to drive configuration and routing decisions.
NDJSON (Newline Delimited JSON)
A format where each line of a file or stream is a valid, self-contained JSON object, used by Docker's json-file logging driver and by docker ps --format json in Docker 25+.

Frequently asked questions

What is daemon.json in Docker?

daemon.json is Docker's global configuration file, read at daemon startup. On Linux it's at /etc/docker/daemon.json; on macOS at ~/.docker/daemon.json. It configures the default logging driver, log rotation, registry mirrors, DNS, and TLS settings. Changes require restarting the Docker daemon with sudo systemctl restart docker (Linux) or restarting Docker Desktop (macOS). Invalid JSON in daemon.json prevents Docker from starting entirely — always validate the file with a JSON linter or Jsonic's JSON formatter before restarting the daemon in production. Keep a backup of a known-good configuration so you can restore quickly if the daemon fails to start after a change.

Where are Docker container logs stored?

The default json-file driver stores logs at /var/lib/docker/containers/<container-id>/<container-id>-json.log on the host. Each line is a JSON object with 3 keys: log (the output text with trailing newline), stream (stdout or stderr), and time (RFC 3339 timestamp). View logs with docker logs <container> — Docker parses the JSON and returns just the log field. To limit disk usage, set max-size and max-file log options in daemon.json for a global default or per-container with --log-opt max-size=10m --log-opt max-file=3.

How do I parse docker inspect output with jq?

docker inspect <container> returns a JSON array. Pipe to jq '.[0]' to get the object, then chain field access: jq '.[0].NetworkSettings.IPAddress' for IP, jq '.[0].Config.Env[]' for environment variables, jq '.[0].State.Status' for running/stopped state. For multiple containers: docker inspect container1 container2 | jq '.[].NetworkSettings.IPAddress'. See jq filter examples for more advanced field selection, filtering, and transformation patterns.

Can I pass a JSON object as a Docker environment variable?

Yes. Environment variables are strings, so JSON must be quoted: docker run -e CONFIG='{"key":"val"}' myimage. In the container, parse with JSON.parse(process.env.CONFIG) (Node.js) or json.loads(os.environ["CONFIG"]) (Python). Shell quoting is tricky with nested quotes — use --env-file with CONFIG={"key":"val"} (no outer quotes needed in env files) to avoid shell interpretation issues. Always wrap the parse call in a try/catch to surface malformed JSON errors at startup with a clear message rather than a silent crash at the point of use.

What is the json-file logging driver?

Docker's default logging driver. It writes each log line as a JSON object to a file on the host. Each entry has 3 keys: log (the text with trailing \n), stream (stdout or stderr), and time (RFC 3339 timestamp). Set rotation globally in daemon.json with max-size and max-file, or per-container with --log-opt max-size=10m --log-opt max-file=3. For applications emitting JSON structured logging, the driver wraps the JSON string inside the log field — use jq -R 'fromjson | .log | fromjson' to parse the double-wrapped output.

How do I use docker ps with JSON output?

Docker 25+ supports docker ps --format json which outputs each container as a compact JSON object per line (NDJSON format). Pipe to jq for filtering: docker ps --format json | jq 'select(.Status | contains("Up")) | .Names'. This is the same NDJSON log format used by the json-file logging driver. Older Docker versions use --format '{{json .}}' for JSON output from docker ps, docker images, and other commands that support Go template formatting.

Ready to work with Docker JSON configs?

Use Jsonic's JSON Formatter to validate daemon.json, inspect docker inspect output, and pretty-print container configuration before deploying. You can also diff two JSON snapshots to compare container state before and after a configuration change.

Open JSON Formatter