Docker logging cheatsheet.
Default: json-file
docker logs web
docker logs -f web
docker logs --tail 100 web
docker logs --since 10m web
docker logs --until 2026-01-15T12:00:00 web
docker logs -t web # timestamps
Stored as JSON at /var/lib/docker/containers/<id>/<id>-json.log.
Log rotation
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"compress": "true"
}
}
Per-container:
docker run --log-opt max-size=10m --log-opt max-file=3 app
Compose:
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
⚠️ Without rotation, logs grow unbounded → disk full.
Logging drivers
json-file: default.local: more efficient than json-file.journald: systemd journal (Linux).syslog: forward to syslog daemon.fluentd: forward to Fluentd.gelf: Graylog.awslogs: CloudWatch.gcplogs: GCP Cloud Logging.splunk.none: discard.
syslog
docker run \
--log-driver syslog \
--log-opt syslog-address=udp://logserver:514 \
--log-opt tag="docker/{{.Name}}" \
app
fluentd
docker run \
--log-driver fluentd \
--log-opt fluentd-address=fluentd:24224 \
--log-opt tag=docker.{{.Name}} \
app
Fluentd parses + ships to ES / S3 / Loki.
Loki + Promtail (preferred for K8s)
Promtail/Vector tails Docker logs and ships to Grafana Loki. No driver change needed; reads json-file.
App-side: write to stdout
# DO
print(...)
logger.info(...)
# DON'T
logging.FileHandler("app.log") # logs go nowhere
12-factor: write to stdout/stderr, let runtime collect.
Structured logs
import structlog
log = structlog.get_logger()
log.info("user_login", user_id=42, ip="1.2.3.4")
# {"level":"info","event":"user_login","user_id":42,"ip":"1.2.3.4","timestamp":"..."}
Easier to query, parse, alert on.
Multi-line logs
Multi-line tracebacks become multiple log entries. Use:
import logging
# Format with timestamp and inline traceback
Or use \n escapes. Some aggregators handle multi-line via patterns.
Sidecar log forwarder
services:
app:
image: myapp
logging:
driver: json-file
options:
max-size: "10m"
logshipper:
image: grafana/promtail
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail.yml:/etc/promtail/promtail.yml
Promtail / Vector / Fluent Bit on each host.
Discarding logs
services:
noisy:
logging:
driver: none
For containers you don’t care about.
docker events
docker events
docker events --since 1h --filter type=container --filter event=stop
Audit container lifecycle.
In-container log paths
If app writes to a file:
services:
app:
volumes:
- ./logs:/app/logs
Tail from host. Worse than stdout.
Log filtering with grep + jq
docker logs web 2>&1 | grep ERROR
docker logs web 2>&1 | jq 'select(.level=="error")'
Logs with timestamps
docker logs -t --since 1h web
If app already logs timestamps, -t adds Docker’s too (redundant).
Common mistakes
- No log rotation → disk fills.
- Writing to file inside container → vanishes on restart.
- Unstructured logs → can’t filter.
- Logging healthcheck probes → log spam.
- Including PII in logs.
- Forgetting to redirect framework errors to stdout.
Read this next
If you want my structured logging templates, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .