Docker JSON Configuration: daemon.json, Inspect Output & Compose
Last updated:
Docker uses JSON throughout its ecosystem — daemon.json configures the Docker daemon, docker inspect returns full container/image/network metadata as JSON arrays, and the Docker Engine API communicates exclusively over JSON. A docker inspect call on a single container returns a 150-field JSON array; piping through jq '.[0].NetworkSettings.IPAddress' extracts just the container IP in under 1 ms, faster than parsing the full docker ps table output. This guide covers daemon.json configuration options, querying docker inspect output with jq and Go templates, Docker Compose JSON schema validation, Docker health check JSON, and the Docker Engine API JSON endpoints.
daemon.json: Docker Daemon JSON Configuration
daemon.json is the primary JSON configuration file for the Docker daemon. On Linux it lives at /etc/docker/daemon.json; on macOS at ~/.docker/daemon.json; on Windows at C:\ProgramData\docker\config\daemon.json. The file is a flat JSON object — all keys are optional, and any omitted key uses its compiled-in default. After every edit, validate the JSON with dockerd --validate before restarting the daemon; invalid JSON in daemon.json prevents the daemon from starting entirely.
// /etc/docker/daemon.json — annotated production example
{
// Log driver: json-file (default), fluentd, awslogs, splunk, gelf, journald
"log-driver": "json-file",
"log-opts": {
"max-size": "10m", // rotate when log file exceeds 10 MB
"max-file": "3", // keep last 3 rotated files (30 MB total per container)
"compress": "true" // gzip rotated files
},
// Registry mirrors — tried before Docker Hub (reduces rate-limit hits)
"registry-mirrors": [
"https://mirror.example.com",
"https://registry-mirror.example.org"
],
// Allow HTTP (insecure) registries — add local dev registries here
"insecure-registries": ["192.168.1.5:5000", "localhost:5000"],
// Storage driver — overlay2 is recommended for Linux
"storage-driver": "overlay2",
// Custom DNS servers for containers (overrides host /etc/resolv.conf)
"dns": ["8.8.8.8", "8.8.4.4"],
"dns-search": ["internal.example.com"],
// Parallel image layer downloads (default: 3, max: 10)
"max-concurrent-downloads": 5,
"max-concurrent-uploads": 5,
// Keep containers running when daemon restarts (zero-downtime daemon upgrade)
"live-restore": true,
// Custom data root (move from default /var/lib/docker)
"data-root": "/mnt/docker-data",
// Expose daemon on TCP (use TLS in production — never plain TCP on 0.0.0.0)
// "hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2376"],
// Enable experimental features (BuildKit is now stable — not needed in Docker 23+)
"features": { "buildkit": true },
// Default ulimits for all containers
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 64000, "Soft": 64000 }
}
}
# Validate syntax without restarting
sudo dockerd --validate --config-file /etc/docker/daemon.json
# Apply changes — reload is sufficient for log-driver, mirrors, dns changes
sudo systemctl reload docker
# Full restart required for: hosts, data-root, storage-driver, live-restore
sudo systemctl restart docker
# Verify active configuration
docker info --format '{{json .}}' | jq '{LogDriver, RegistryConfig}'Two common daemon.json mistakes: (1) setting "hosts" in daemon.json while also passing -H flags in the systemd unit file — Docker rejects duplicate host configuration with a startup error; remove the -H fd:// flag from the unit file when using daemon.json hosts. (2) Using string values where integers are required — "max-concurrent-downloads": "5" (string) is invalid; it must be 5 (integer). Run sudo dockerd --validate after every edit to catch both JSON syntax errors and type mismatches before they cause a daemon outage.
docker inspect JSON Output and jq Queries
docker inspect returns a JSON array regardless of how many objects you inspect — even a single container returns [{'{...}'}]. The top-level fields of a container inspect object include Id, Created, Path, Args, State, Image, Name, RestartCount, Mounts, Config, NetworkSettings, and HostConfig. Pipe through jq to extract specific fields without processing the entire 150-field response.
# ── Single container — most common jq patterns ─────────────────
# Get container IP address
docker inspect my-container | jq '.[0].NetworkSettings.IPAddress'
# → "172.17.0.3"
# Get container status
docker inspect my-container | jq '.[0].State.Status'
# → "running"
# Get all environment variables
docker inspect my-container | jq '.[0].Config.Env[]'
# → "PATH=/usr/local/bin:/usr/bin:/bin"
# → "NODE_ENV=production"
# Get port bindings (host port ← container port)
docker inspect my-container | jq '.[0].NetworkSettings.Ports'
# → { "3000/tcp": [{ "HostIp": "0.0.0.0", "HostPort": "8080" }] }
# Get mount points
docker inspect my-container | jq '.[0].Mounts[] | {src: .Source, dst: .Destination}'
# Get restart policy
docker inspect my-container | jq '.[0].HostConfig.RestartPolicy'
# → { "Name": "unless-stopped", "MaximumRetryCount": 0 }
# ── Multiple containers — batch inspection ──────────────────────
# Inspect all running containers and extract name + IP
docker inspect $(docker ps -q) | jq '.[] | {name: .Name, ip: .NetworkSettings.IPAddress}'
# Find containers using a specific image
docker inspect $(docker ps -q) | jq '.[] | select(.Config.Image | startswith("nginx")) | .Name'
# ── Go template alternative — no jq dependency ─────────────────
# Extract IP with Go template (faster, no jq process)
docker inspect --format '{{.NetworkSettings.IPAddress}}' my-container
# → 172.17.0.3
# Extract as valid JSON (wraps value in JSON encoding)
docker inspect --format '{{json .NetworkSettings}}' my-container | jq '.'
# Multiple fields with Go template
docker inspect --format 'Name: {{.Name}} | IP: {{.NetworkSettings.IPAddress}} | Status: {{.State.Status}}' my-container
# ── Image inspect ───────────────────────────────────────────────
# Get image creation date and size
docker inspect nginx:latest | jq '.[0] | {created: .Created, size: .Size, arch: .Architecture}'
# List image layers
docker inspect nginx:latest | jq '.[0].RootFS.Layers | length'
# → 7 (number of layers)
# ── Network inspect ─────────────────────────────────────────────
docker network inspect bridge | jq '.[0].Containers | to_entries[] | {name: .value.Name, ip: .value.IPv4Address}'The Go template {{json .Field}} syntax is the fastest way to extract a single JSON subtree — it runs inside the Docker CLI process and avoids spawning a jq child process. Use Go templates in CI scripts where performance matters; use jq for interactive exploration and complex filtering. For batch operations across many containers, docker inspect $(docker ps -q) makes one API call for all containers rather than N calls — always prefer batch inspect over looping.
Docker Compose JSON Schema and Validation
Docker Compose v2 validates compose.yaml against a published JSON Schema on every docker compose command. Running docker compose config performs a dry-run validation: it merges all compose files (including docker-compose.override.yml), resolves environment variables, and outputs the canonical merged YAML — or prints the schema violation that caused the failure. 90% of compose errors are schema violations detectable offline without running any containers.
# compose.yaml — valid v2 format with JSON Schema compliance
services:
web:
image: nginx:1.25
ports:
- "8080:80" # string format — NOT 8080 (integer)
environment:
NODE_ENV: production
PORT: "3000" # string value for env vars
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s # duration string — NOT 30 (integer)
timeout: 10s
retries: 3
start_period: 40s
deploy:
replicas: 2
resources:
limits:
cpus: "0.5" # string — NOT 0.5 (float)
memory: 512M
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
# ── Validate without starting containers ────────────────────────
docker compose config # validates + outputs merged canonical YAML
docker compose config --quiet # validates only, no output (exit code 1 on error)
# ── Common schema violations ────────────────────────────────────
# WRONG: port as integer
ports:
- 8080 # ← schema error: must be string "8080:80" or mapping
# WRONG: healthcheck interval as integer
healthcheck:
interval: 30 # ← schema error: must be duration string "30s"
# WRONG: cpus as float
resources:
limits:
cpus: 0.5 # ← must be string "0.5"
# WRONG: invalid top-level service key
services:
web:
link: other # ← "link" is not a valid key — correct is "links"
# ── VS Code real-time validation ────────────────────────────────
# Add to compose.yaml top line for IDE autocomplete + red underlines:
# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
# ── ajv CLI for offline schema validation ───────────────────────
# npm install -g ajv-cli js-yaml
# ajv validate -s compose-spec.json -d <(js-yaml compose.yaml)The Compose JSON Schema is published at the compose-spec GitHub repository and covers every valid key for services, networks, volumes, configs, and secrets. The VS Code YAML extension (redhat.vscode-yaml) automatically applies the schema when it detects a compose.yaml or docker-compose.yml filename, providing autocomplete and real-time validation without any additional configuration. For CI pipelines, docker compose config --quiet exits with code 1 on any schema violation — add it as a pre-merge check to catch compose errors before deployment.
Docker Engine API JSON Endpoints
The Docker Engine API is a versioned REST API that communicates exclusively over JSON. By default it listens on a Unix socket at unix:///var/run/docker.sock; every docker CLI command is a JSON API call under the hood. The API version is included in the URL path (e.g., /v1.44/containers/json) but can be omitted to use the daemon's current version. Direct API access is useful for automation, monitoring agents, and CI systems that cannot install the Docker CLI.
# ── List containers — GET /containers/json ─────────────────────
# All running containers (default)
curl --unix-socket /var/run/docker.sock http://localhost/containers/json | jq '.'
# All containers including stopped (?all=true)
curl --unix-socket /var/run/docker.sock 'http://localhost/containers/json?all=true' | jq '.[].Names'
# Filter by status
curl -s --unix-socket /var/run/docker.sock 'http://localhost/containers/json?filters={"status":["running"]}' | jq '.[] | {id: .Id[:12], name: .Names[0], image: .Image}'
# Limit results
curl --unix-socket /var/run/docker.sock 'http://localhost/containers/json?limit=5' | jq '.'
# ── Get specific container JSON — GET /containers/{id}/json ─────
# Equivalent to: docker inspect <container>
curl --unix-socket /var/run/docker.sock http://localhost/containers/my-container/json | jq '.NetworkSettings.IPAddress'
# ── Create a container — POST /containers/create ───────────────
curl --unix-socket /var/run/docker.sock -H 'Content-Type: application/json' -X POST -d '{
"Image": "nginx:1.25",
"ExposedPorts": { "80/tcp": {} },
"HostConfig": {
"PortBindings": { "80/tcp": [{ "HostPort": "8080" }] },
"RestartPolicy": { "Name": "unless-stopped" }
},
"Env": ["NGINX_HOST=example.com"],
"Labels": { "com.example.env": "production" }
}' http://localhost/containers/create?name=my-nginx | jq '.'
# → { "Id": "abc123...", "Warnings": [] }
# Then start it:
curl --unix-socket /var/run/docker.sock -X POST http://localhost/containers/my-nginx/start
# ── Streaming events — GET /events (newline-delimited JSON) ─────
# Each line is a complete JSON object — one event per container action
curl --unix-socket /var/run/docker.sock 'http://localhost/events' &
# → {"status":"start","id":"abc...","from":"nginx","Type":"container","Action":"start","time":1716116400}
# → {"status":"die","id":"abc...","from":"nginx","Type":"container","Action":"die","time":1716116410}
# Filter events by type
curl --unix-socket /var/run/docker.sock 'http://localhost/events?filters={"type":["container"],"event":["die","oom"]}'
# ── Remote API — TCP with TLS ───────────────────────────────────
# In daemon.json: "hosts": ["tcp://0.0.0.0:2376"]
# With TLS certificates:
export DOCKER_HOST=tcp://remote-host:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=~/.docker/certs
docker ps # now connects to remote daemon over TLS
# ── API version discovery ───────────────────────────────────────
curl --unix-socket /var/run/docker.sock http://localhost/version | jq '{Version, ApiVersion, MinAPIVersion}'The /events endpoint streams newline-delimited JSON (NDJSON) — one complete JSON object per line, with no outer array wrapper. Parse it line-by-line: in Python use for line in response.iter_lines() and json.loads(line); in Node.js use a readline stream. Never parse the stream as a single JSON document — it is intentionally not valid JSON as a whole. For production monitoring agents, always pin the API version in the URL (/v1.44/) to avoid breakage when the daemon upgrades and the default version changes.
Container Log JSON: Log Drivers and Structured Logging
With the default json-file log driver, Docker writes each log line as a JSON object on disk. Understanding this format is essential for log parsing, rotation configuration, and switching to centralized logging drivers. The log file is stored at /var/lib/docker/containers/<id>/<id>-json.log and grows unboundedly unless rotation is configured in daemon.json.
# ── json-file log entry format ──────────────────────────────────
# Each line in <container-id>-json.log is one JSON object:
{"log":"Server started on port 3000
","stream":"stdout","time":"2026-05-19T10:00:00.123456789Z"}
{"log":"ERROR: connection refused
","stream":"stderr","time":"2026-05-19T10:00:01.456789012Z"}
# Fields:
# "log" — the raw log line including trailing
# "stream" — "stdout" or "stderr"
# "time" — RFC3339Nano timestamp
# ── Reading and filtering log JSON files ────────────────────────
LOG="/var/lib/docker/containers/<id>/<id>-json.log"
# Pretty print all entries
sudo jq '.' "$LOG"
# Filter only stderr
sudo jq 'select(.stream == "stderr")' "$LOG"
# Extract plain log lines (strip JSON wrapper, remove trailing
)
sudo jq -r '.log' "$LOG"
# Filter by time range
sudo jq 'select(.time >= "2026-05-19T10:00:00Z" and .time <= "2026-05-19T11:00:00Z")' "$LOG"
# Count errors
sudo jq 'select(.stream == "stderr") | .log' "$LOG" | wc -l
# ── Configure log rotation in daemon.json ───────────────────────
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m", // 10 MB per file before rotation
"max-file": "3", // keep 3 files = 30 MB max per container
"compress": "true", // gzip rotated files (saves ~80% disk space)
"labels": "app,env", // include container labels as log metadata
"env": "NODE_ENV" // include this env var value in log metadata
}
}
# ── Alternative log drivers ─────────────────────────────────────
# fluentd — forward to Fluentd/Fluent Bit aggregator
{
"log-driver": "fluentd",
"log-opts": {
"fluentd-address": "localhost:24224",
"tag": "docker.{{.Name}}"
}
}
# awslogs — ship directly to CloudWatch Logs
{
"log-driver": "awslogs",
"log-opts": {
"awslogs-region": "us-east-1",
"awslogs-group": "/docker/production",
"awslogs-stream": "{{.Name}}"
}
}
# ── Structured logging from your application ────────────────────
# Write JSON to stdout — Docker stores it as the "log" field value
# Application (Node.js):
console.log(JSON.stringify({ level: "info", msg: "Request processed", duration_ms: 42, path: "/api/users" }))
# In the log file:
# {"log":"{"level":"info","msg":"Request processed","duration_ms":42}
","stream":"stdout","time":"..."}
# Extract nested JSON from log field:
sudo jq '.log | fromjson | select(.level == "error")' "$LOG"Application-level structured logging (writing JSON to stdout) works well with the json-file driver but requires double-parsing: Docker wraps the JSON string in its own JSON object, so the application JSON ends up as a string value in the log field. Use jq '.log | fromjson' to unwrap it. For high-volume production logging, switch to fluentd or awslogs driver to avoid disk pressure and enable centralized log aggregation — the json-file driver has no built-in shipping and requires a separate log collector to read the files.
Docker Health Check JSON
Docker health checks run a command inside the container on a configurable interval and store the last 5 results as JSON in the container inspect output under State.Health. Health status drives orchestration decisions: Docker Swarm and Compose depends_on: condition: service_healthy both wait for State.Health.Status to become "healthy" before starting dependent services.
# ── Dockerfile HEALTHCHECK instruction ─────────────────────────
# Exec form (preferred — avoids shell interpolation)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD ["curl", "-f", "http://localhost:3000/health"]
# Shell form alternative
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost:3000/health || exit 1
# Exit codes:
# 0 = healthy
# 1 = unhealthy
# 2 = reserved (do not use)
# Disable health check inherited from base image
HEALTHCHECK NONE
# ── Health status JSON structure ────────────────────────────────
# docker inspect <container> | jq '.[0].State.Health'
{
"Status": "healthy", // "starting" | "healthy" | "unhealthy"
"FailingStreak": 0, // consecutive failures since last healthy
"Log": [
{
"Start": "2026-05-19T10:00:00.123456789Z",
"End": "2026-05-19T10:00:00.234567890Z",
"ExitCode": 0, // 0 = healthy, 1 = unhealthy
"Output": "" // stdout+stderr of the health check command
},
{
"Start": "2026-05-19T10:00:30.123456789Z",
"End": "2026-05-19T10:00:35.234567890Z",
"ExitCode": 1,
"Output": "curl: (7) Failed to connect to localhost port 3000"
}
// ... up to 5 most recent results
]
}
# ── Querying health status with docker inspect ──────────────────
# Get full health JSON (pretty printed)
docker inspect --format '{{json .State.Health}}' my-container | jq '.'
# Get just the status string
docker inspect --format '{{.State.Health.Status}}' my-container
# → healthy
# Get failing streak count
docker inspect my-container | jq '.[0].State.Health.FailingStreak'
# Get last health check output (useful for debugging failures)
docker inspect my-container | jq '.[0].State.Health.Log[-1].Output'
# Get all failed health check outputs
docker inspect my-container | jq '.[0].State.Health.Log[] | select(.ExitCode != 0) | .Output'
# ── Health check in compose.yaml ───────────────────────────────
services:
api:
image: my-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s # grace period before first check counts
worker:
image: my-worker:latest
depends_on:
api:
condition: service_healthy # waits for api health JSON Status = "healthy"
# ── Health check script that outputs JSON status ────────────────
#!/bin/sh
# /usr/local/bin/healthcheck.sh
DB_STATUS=$(pg_isready -U postgres -q && echo "ok" || echo "error")
HTTP_STATUS=$(curl -sf http://localhost:3000/health -o /dev/null && echo "ok" || echo "error")
echo "{\"db\": \"$DB_STATUS\", \"http\": \"$HTTP_STATUS\"}"
[ "$DB_STATUS" = "ok" ] && [ "$HTTP_STATUS" = "ok" ] && exit 0 || exit 1The start_period option is critical for slow-starting containers (databases, JVM apps): health check failures during the start period do not count toward the retries limit, but the container is still marked "starting" rather than "unhealthy". Set start_period to your P99 cold-start time plus a buffer. For debugging unhealthy containers, docker inspect my-container | jq '.[0].State.Health.Log[-1].Output' shows exactly what the last health check command printed — more useful than the status alone.
Dockerfile JSON Array Syntax for CMD and ENTRYPOINT
CMD and ENTRYPOINT in a Dockerfile support two forms: exec form (a JSON array) and shell form (a plain string). The choice affects signal handling, argument passing, and how the process becomes PID 1 in the container. Exec form is the correct default for production images; shell form exists for convenience but introduces signal handling bugs that cause slow or failed container shutdowns.
# ── Exec form (JSON array) — PREFERRED ─────────────────────────
# Docker runs the command directly — no shell, process is PID 1
CMD ["node", "server.js"]
ENTRYPOINT ["node", "server.js"]
# ── Shell form — AVOID in production ───────────────────────────
# Docker runs: /bin/sh -c "node server.js"
# sh becomes PID 1 — SIGTERM goes to sh, not node
# node may not receive the signal → container hangs until SIGKILL timeout
CMD node server.js
ENTRYPOINT node server.js
# ── Why exec form matters: signal handling ──────────────────────
# Shell form process tree:
# PID 1: /bin/sh -c "node server.js"
# PID 2: node server.js
# docker stop sends SIGTERM → sh receives it → sh may or may not forward
# After 10s grace period → SIGKILL to sh → sh dies → node is orphaned → SIGKILL
# Exec form process tree:
# PID 1: node server.js
# docker stop sends SIGTERM → node receives it directly
# node can do graceful shutdown (close DB connections, finish requests)
# ── Common exec form mistakes ───────────────────────────────────
# WRONG: single string with space — passes one argument "node server.js"
CMD ["node server.js"] # ← node tries to run a file named "node server.js"
# CORRECT: each argument is a separate array element
CMD ["node", "server.js"]
# WRONG: shell operators in exec form — no shell to interpret them
CMD ["sh", "-c", "node server.js && echo done"] # works (explicit sh)
CMD ["node server.js && echo done"] # WRONG — no shell
# ── ENTRYPOINT + CMD composition ───────────────────────────────
# ENTRYPOINT = executable (never overridden without --entrypoint flag)
# CMD = default arguments (overridden by arguments after image name in docker run)
ENTRYPOINT ["python3", "-m", "myapp"]
CMD ["--port", "8080"] # docker run myimage → python3 -m myapp --port 8080
# docker run myimage --port 9090 → python3 -m myapp --port 9090
# ── Tini as init process (zombie reaping) ──────────────────────
# If your app spawns child processes, use tini as PID 1
FROM node:20-alpine
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]
# tini becomes PID 1, forwards signals to node, reaps zombie children
# ── Validating Dockerfile JSON syntax with hadolint ────────────
# hadolint catches exec/shell form issues and other Dockerfile best practices
# docker run --rm -i hadolint/hadolint < Dockerfile
# DL3025: Use JSON arguments to CMD and ENTRYPOINT
# DL3007: Using latest is best avoided
# ── Multi-stage example with proper CMD ────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
# Exec form — no shell, PID 1, proper signal handling
CMD ["node", "dist/server.js"]Validate Dockerfile CMD/ENTRYPOINT syntax with hadolint (rule DL3025) in CI — it catches shell form usage and flags it as a warning. For containers that need to run multiple processes or handle child process cleanup, use tini as PID 1 with exec form: ENTRYPOINT ["/sbin/tini", "--"] followed by CMD ["your-app"]. Tini forwards signals correctly and reaps zombie processes, solving the two main problems that shell form introduces. See the JSON config management guide for patterns on externalizing container configuration.
Key Terms
- daemon.json
- The JSON configuration file for the Docker daemon, located at
/etc/docker/daemon.jsonon Linux. It is a JSON object with optional keys that configure daemon-wide settings including log driver, registry mirrors, storage driver, DNS, and resource limits. The daemon reads this file at startup; changes require a daemon restart or reload to take effect. Validate syntax withdockerd --validatebefore restarting — invalid JSON indaemon.jsonprevents the daemon from starting and takes down all containers on the host. - docker inspect
- A CLI command that returns detailed JSON metadata for containers, images, networks, and volumes. It always returns a JSON array, even for a single object. The output includes all configuration, runtime state, network settings, mounts, environment variables, and health check results for the inspected object. Pipe through
jqfor field extraction or use--format '{{json .Field}}'for Go template-based extraction.docker inspectmakes a single API call toGET /containers/{id}/jsonor equivalent endpoint on the Docker Engine API. - exec form
- The JSON array syntax for Dockerfile
CMD,ENTRYPOINT, andRUNinstructions:CMD ["node", "server.js"]. In exec form, Docker runs the specified executable directly without invoking a shell, making the process PID 1 in the container. This enables proper signal handling —SIGTERMfromdocker stopis delivered directly to the process — and avoids shell interpolation of arguments. Contrast with shell form (CMD node server.js), where Docker invokes/bin/sh -cand the shell becomes PID 1, often causing signal handling failures. - log driver
- A Docker plugin that receives container stdout/stderr and writes or forwards it to a destination. The default log driver is
json-file, which writes each log line as a JSON object to disk at/var/lib/docker/containers/<id>/<id>-json.log. Other drivers includefluentd(forward to Fluentd/Fluent Bit),awslogs(ship to CloudWatch Logs),splunk,gelf, andjournald. Configure the default driver and its options indaemon.jsonunder"log-driver"and"log-opts"; override per-container withdocker run --log-driver. - health check
- A Dockerfile instruction or Compose configuration that defines a command Docker runs periodically inside the container to test its health. The command exit code determines status: 0 = healthy, 1 = unhealthy. Docker stores the last 5 results as a JSON array in
State.Health.Log, accessible viadocker inspect. Health status ("starting","healthy","unhealthy") is used by Docker Swarm, Composedepends_on, and orchestrators to make routing and scheduling decisions. Configure with--interval,--timeout,--retries, and--start-periodflags. - Docker Engine API
- The RESTful HTTP API that the Docker daemon exposes over a Unix socket (
/var/run/docker.sock) or optionally over TCP. Every Docker CLI command is implemented as an API call —docker pscallsGET /containers/json,docker inspectcallsGET /containers/{id}/json. Request and response bodies are JSON; the/eventsendpoint returns newline-delimited JSON (NDJSON). Access it directly withcurl --unix-socketfor automation, monitoring, or environments where the Docker CLI is not available. API versions are stable; always pin the version in the URL path for production clients.
FAQ
Where is the Docker daemon.json file and what can I configure in it?
On Linux, daemon.json is at /etc/docker/daemon.json. On macOS it is at ~/.docker/daemon.json, and on Windows at C:\ProgramData\docker\config\daemon.json. The file is a JSON object configuring over 40 daemon settings: "log-driver" and "log-opts" control logging for all containers; "registry-mirrors" is a JSON array of mirror URLs tried before Docker Hub; "insecure-registries" allows HTTP registries; "storage-driver" sets overlay2 or other drivers; "dns" overrides DNS for all containers; "max-concurrent-downloads" controls parallel layer pulls; "live-restore" keeps containers running during daemon restart; and "data-root" moves Docker's data directory. Validate with dockerd --validate before restarting; invalid JSON prevents daemon startup.
How do I parse docker inspect JSON output with jq?
docker inspect always returns a JSON array. Common patterns: docker inspect <container> | jq '.[0].NetworkSettings.IPAddress' extracts the container IP; jq '.[0].State.Status' gets running/stopped status; jq '.[0].Config.Env[]' lists all environment variables; jq '.[0].Mounts' shows volume mounts. For multiple containers: docker inspect $(docker ps -q) | jq '.[] | {.name: .Name, ip: .NetworkSettings.IPAddress}'. The Go template alternative docker inspect --format '{{json .NetworkSettings}}' <container> outputs JSON for a specific field without spawning a jq process — faster for CI scripts. Use --format '{{.NetworkSettings.IPAddress}}' (without json) for plain string output.
How do I validate a Docker Compose file JSON schema?
Run docker compose config to validate your compose.yaml against the Compose JSON Schema — it merges all compose files, resolves environment variables, and prints the canonical merged YAML, or exits with an error describing the schema violation. Common violations: ports as integers instead of strings ("8080:80"), healthcheck interval as an integer instead of a duration string ("30s"), and cpus as a float instead of a string ("0.5"). For real-time IDE validation, add # yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json to the top of your compose file and install the VS Code YAML extension. In CI, use docker compose config --quiet — it exits with code 1 on any schema error with no output, suitable for pre-merge checks.
How do I use the Docker Engine API to get container JSON?
The Docker Engine API is available at unix:///var/run/docker.sock by default. List running containers: curl --unix-socket /var/run/docker.sock http://localhost/containers/json | jq '.'. Include stopped containers with ?all=true. Get a specific container's full JSON (equivalent to docker inspect): curl --unix-socket /var/run/docker.sock http://localhost/containers/<id>/json. Create a container: POST to /containers/create with a JSON body containing Image, Cmd, Env, and HostConfig. The /events endpoint streams newline-delimited JSON — one JSON object per container event. Filter by status: add ?filters={"status":["running"]} to the containers endpoint. For remote daemons, configure TLS and set DOCKER_HOST=tcp://host:2376.
What JSON format does Docker use for container logs?
With the json-file driver, Docker writes each log line as a JSON object: {"log":"message\n","stream":"stdout","time":"2026-05-19T10:00:00.123456789Z"}. The log field contains the raw line including the trailing newline; stream is "stdout" or "stderr"; time is an RFC3339Nano timestamp. Log files are at /var/lib/docker/containers/<id>/<id>-json.log. Filter stderr: sudo jq 'select(.stream == "stderr")' logfile. Extract plain text: sudo jq -r '.log' logfile. Configure rotation in daemon.json via "log-opts": {"max-size": "10m", "max-file": "3"} to cap logs at 30 MB per container. For application-level JSON logs, use jq '.log | fromjson' to unwrap the nested JSON string.
How do I check Docker container health status as JSON?
Run docker inspect --format '{{json .State.Health}}' <container> | jq '.' to get the full health JSON. The object contains Status ("starting", "healthy", or "unhealthy"), FailingStreak (consecutive failure count), and Log (array of last 5 results, each with Start, End, ExitCode, and Output). Get just the status string: docker inspect --format '{{.State.Health.Status}}' <container>. Debug failures with the last check output: docker inspect <container> | jq '.[0].State.Health.Log[-1].Output'. Health status drives depends_on: condition: service_healthy in Compose and determines whether Docker Swarm routes traffic to a replica. Exit code 0 = healthy, 1 = unhealthy, 2 = reserved.
What is the difference between CMD exec form (JSON array) and shell form?
Exec form (CMD ["node", "server.js"]) runs the process directly as PID 1 — SIGTERM from docker stop is delivered directly to the process, enabling graceful shutdown. Shell form (CMD node server.js) runs /bin/sh -c "node server.js" — the shell becomes PID 1 and may not forward signals to the Node.js child process, causing the container to hang until the 10-second grace period expires and Docker sends SIGKILL. Use exec form for all production images. Common exec form mistake: CMD ["node server.js"] with one element passes a single argument with a space — correct is CMD ["node", "server.js"] with separate elements. Validate with hadolint (rule DL3025). For ENTRYPOINT + CMD composition, both must be exec form for argument override to work correctly.
How do I configure Docker registry mirrors in daemon.json?
Add "registry-mirrors" to /etc/docker/daemon.json as a JSON array of mirror URLs: {"registry-mirrors": ["https://mirror.example.com"]}. Mirror URLs must include https:// and must not have a trailing slash. Docker tries mirrors in order before falling back to Docker Hub, reducing pull latency and rate-limit hits. After editing, restart the daemon with sudo systemctl restart docker (reload is insufficient for mirror changes). Verify with docker info | grep -A 5 'Registry Mirrors'. For corporate environments, use a local registry (Harbor, Nexus, or Artifactory) configured as a pull-through cache — it caches images locally and removes Docker Hub dependency entirely. For HTTP registries, use "insecure-registries" (a separate key) rather than "registry-mirrors".
Further reading and primary sources
- Docker daemon.json reference — Official reference for all daemon.json configuration options and their types
- Docker Engine API reference — Full API reference for Docker Engine REST+JSON endpoints
- Compose Specification JSON Schema — The JSON Schema used to validate Docker Compose files
- jq manual — Complete jq filter reference for parsing and transforming JSON output
- hadolint Dockerfile linter — Linter that enforces Dockerfile best practices including exec-form CMD/ENTRYPOINT