DEV Community

Peon Sh
Peon Sh

Posted on Originally published at peon.sh

Docker Logging Best Practices for Production Apps

Log to stdout, structure as JSON, cap file sizes and know your retention: pragmatic logging for containerized apps without an ELK stack.

The one rule: stdout, unbuffered
The twelve-factor principle remains the foundation of container logging: applications write events to stdout/stderr and treat log routing as the runtime’s job. Never write log files inside a container, they die with it, they hide from docker logs, and inside volumes they grow until the disk fills. Every serious runtime, platform and collector builds on the stdout convention; fighting it buys you nothing.

Unbuffered matters too: Python needs PYTHONUNBUFFERED=1, and any language buffering stdout will show logs minutes late or lose the crucial lines before a crash.

Structure beats prose
The difference between grep-able text and queryable JSON shows up the first time you debug a real incident. JSON lines with a level, timestamp, message and request context turn "search the haystack" into "filter where user_id=X and status=500". Every mainstream logger does this well: pino (Node), zerolog/slog (Go), structlog (Python), Serilog (.NET).

  • Include a request ID on every line of a request’s lifecycle, correlation is the whole game

  • Log at boundaries (request in/out, job start/end, external calls) rather than narrating every function

  • Never log secrets, tokens or full card numbers; add a redaction layer where user data flows

{"level":"error","time":"2026-04-07T10:31:04Z","req_id":"abc123",
"user_id":8841,"route":"/api/checkout","status":500,
"err":"payment provider timeout after 3000ms","duration_ms":3012}

Cap and rotate at the daemon
Docker’s default json-file driver has no size cap, making unbounded logs the number-one cause of mysteriously full Docker hosts. Fix it once, globally:

/etc/docker/daemon.json

{ "log-opts": { "max-size": "20m", "max-file": "3" } }
# 60 MB ceiling per container; restart docker, recreate containers to adopt
Enter fullscreen mode Exit fullscreen mode

Levels and volume discipline
Run production at info level: debug in production drowns signals and inflates costs everywhere downstream. A useful volume heuristic: a healthy request logs 1 to 3 lines, not 30. If a single user action produces a screen of logs, you are narrating rather than reporting, and the noise will hide the one line that matters during an incident.

Do you actually need a log stack?
For single-host and few-host deployments, platform log access, Peon streams live and recent container logs per service in the dashboard, plus daemon-level rotation covers the daily debugging loop: see the error, correlate by request ID, fix. Graduate to Loki or an ELK stack when a concrete need arrives: searching across many servers at once, retention measured in months for compliance, or alerting on log patterns. Adopting that infrastructure before the need is a classic complexity trap; the migration later is easy precisely because everything already logs structured JSON to stdout.

Top comments (0)