and production-ready logging strategies
If you only ever read Docker logs with docker logs, you lose the overview fast in production. Structured logging over stdout and stderr, the right log driver, and a centralized log management system are what make container logs observable, searchable, and alertable.
Table of Contents
- 1. The core principle: stdout and stderr in Docker
- 2. docker logs: capabilities and limits
- 3. Log drivers: json-file, journald, syslog, and more
- 4. Log rotation: keeping disk usage under control
- 5. Structured logging: JSON format and labels
- 6. Centralized logging with Loki and Promtail
- 7. Fluentd and Fluent Bit as log aggregators
- 8. Configuring logging correctly in your applications
- 9. Log drivers compared
- 10. Summary
- 11. FAQ
1. The core principle: stdout and stderr in Docker
Docker is built on a simple concept: every container should write its output exclusively to stdout and stderr, instead of directly into log files. This is not an arbitrary convention, it follows the Unix principle that a process should not need to know anything about its runtime environment: it emits output, and the infrastructure decides what happens to it. Docker captures these streams and forwards them to the configured log driver. That way, the Docker daemon manages Docker logs centrally, regardless of which application runs inside the container.
This principle has far-reaching consequences: an application that writes to a file inside the container produces no Docker logs at all. That output is only reachable through a bind mount or volume, cannot be queried with docker logs, and disappears when the container is removed. For production-ready container infrastructure, writing to stdout and stderr is therefore not optional, it is mandatory. Anyone containerizing an existing application often has to check whether the logging configuration was adjusted accordingly, since many frameworks write to files by default, not to the standard streams.
One important technical detail: stdout and stderr are handled internally as two separate streams and are tagged in Docker logs with the attribute stream: stdout or stream: stderr. The flag docker logs 2>/dev/null suppresses stderr, and docker logs 1>/dev/null suppresses stdout. That is handy while debugging, when you only want to see error messages or only normal output.
2. docker logs: capabilities and limits
The docker logs command is the most obvious tool for working with Docker logs, but it has clear limits in production. With --follow you can tail logs in real time, with --since and --until you can narrow a time window, and with --tail you can retrieve the last N lines. For simple diagnostics on a single container, that is enough. But as soon as multiple containers, multiple nodes, or a Kubernetes cluster enter the picture, docker logs falls short: there is no cross-container search, no aggregation, and no persistence beyond a container restart.
Another problem: by default, Docker stores Docker logs as JSON files under /var/lib/docker/containers/<ID>/<ID>-json.log. Without rotation, that file grows without limit. In production environments with many containers, this can eat up the entire disk on the host in a short time. This issue is well documented and a recurring cause of production incidents, not because developers are unaware of it, but because it gets forgotten in the initial infrastructure setup.
# Basic log access commands
docker logs my-container
docker logs --follow --tail 100 my-container
# Time-based filtering
docker logs --since 2h my-container
docker logs --since "2026-05-09T08:00:00" --until "2026-05-09T09:00:00" my-container
# Separate stdout and stderr
docker logs my-container 2>/dev/null # stdout only
docker logs my-container 1>/dev/null # stderr only
# Find the raw JSON log file location
docker inspect --format='{{.LogPath}}' my-container
# Check log file size on disk
docker inspect --format='{{.LogPath}}' my-container | xargs wc -c
# Follow logs across multiple containers using docker compose
docker compose logs --follow --tail 50 web php
3. Log drivers: json-file, journald, syslog, and more
Docker supports several log drivers, which determine where Docker logs end up. The default driver, json-file, writes every log line as JSON into files on the host. That is convenient for development environments, but it is usually the worst choice for production, since it includes no automatic rotation, no forwarding, and no central management. The journald driver forwards Docker logs into the systemd journal, which gives Linux hosts running systemd a consistent integration into their existing log infrastructure. journalctl -u docker-<container-id> then returns all container logs with the usual journal filtering options.
For teams running a centralized log system, the drivers gelf (Graylog Extended Log Format), fluentd, and awslogs are relevant. These forward Docker logs directly to an external system without storing them on the host. That comes with a significant downside: if the external system is unreachable, logging blocks and the container can no longer write output, or logs get dropped, depending on the configuration. The local driver (available since Docker 20.10) stores logs in a compressed binary format and rotates automatically, but it is not queryable through the Docker API.
4. Log rotation: keeping disk usage under control
Log rotation is one of the most critical settings for production Docker logs. Without it, log files grow indefinitely until the host disk fills up and new processes can no longer write files at all. That affects not just the application itself, but the entire host, including every other container. Rotation can be configured either globally in the Docker daemon or per container in the compose file or the docker run command.
The recommended setup combines a maximum file size (max-size) with a maximum number of files (max-file). With max-size: 10m and max-file: 5, at most 50 MB per container is reserved for Docker logs. Docker rotates the file automatically once it reaches the configured size. Important detail: rotation deletes the oldest file once the maximum count is exceeded. When planning capacity, you therefore need to calculate how many containers run simultaneously and how much total log volume that can produce.
# Global log rotation in /etc/docker/daemon.json
# Apply with: systemctl restart docker
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5",
"compress": "true"
}
}
# Per-container override in docker-compose.yml
services:
web:
image: nginx:alpine
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
labels: "app,environment"
# Use 'local' driver for better compression (Docker 20.10+)
app:
image: myapp:latest
logging:
driver: "local"
options:
max-size: "20m"
max-file: "7"
# Check actual disk usage of Docker logs
du -sh /var/lib/docker/containers/*/
find /var/lib/docker/containers -name "*-json.log" -exec ls -lh {} \;
5. Structured logging: JSON format and labels
Structured Docker logs in JSON format are the decisive difference between logs you can read and logs you can actually analyze. A plaintext log line like ERROR: connection failed has to be parsed with regular expressions to extract fields such as severity, timestamp, and context. A structured log in JSON already contains those fields: {"level":"error","msg":"connection failed","service":"checkout","duration_ms":3200}. Log management systems can then filter and aggregate directly on those fields.
Docker container labels play an important role here: with the log driver option labels or env, container labels or environment variables get automatically attached to every log entry. That way, metadata like app=checkout, environment=production, or version=2.4.1 shows up in every log line, without the application itself needing to know that information. This is especially valuable when several versions of an application run at the same time and logs need to be filtered by version.
6. Centralized logging with Loki and Promtail
Grafana Loki is a log aggregation system built specifically for container environments and it integrates seamlessly with Grafana. Unlike Elasticsearch, Loki does not store the full content of Docker logs, only metadata and compressed log streams. That makes it much lighter on resources and more cost-efficient for teams running their own infrastructure. Promtail is the companion agent that runs on every Docker host, reads Docker logs from the JSON files, enriches them with labels, and ships them to Loki.
Configuring a Promtail agent for Docker is fairly straightforward: you point it at the path of the Docker log files, define labels based on the container name and service metadata, and set the Loki endpoint. In Grafana, you can then write log queries in LogQL that filter by service, severity, or any JSON field. Integrating with Grafana dashboards lets you display Docker logs right alongside metrics from Prometheus, correlated in time, which speeds up troubleshooting considerably.
# docker-compose.yml: Loki + Promtail stack
services:
loki:
image: grafana/loki:3.0.0
ports:
- "3100:3100"
volumes:
- loki_data:/loki
- ./loki-config.yml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:3.0.0
volumes:
# Mount Docker socket to discover containers
- /var/run/docker.sock:/var/run/docker.sock:ro
# Mount log directory for reading
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail-config.yml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
grafana:
image: grafana/grafana:10.4.0
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=secret
volumes:
- grafana_data:/var/lib/grafana
volumes:
loki_data:
grafana_data:
# promtail-config.yml: scrape Docker log files
# scrape_configs:
# - job_name: docker
# docker_sd_configs:
# - host: unix:///var/run/docker.sock
# refresh_interval: 5s
7. Fluentd and Fluent Bit as log aggregators
Fluentd and its lightweight sibling Fluent Bit are the most established log aggregators in the CNCF ecosystem. Fluentd is supported directly as a Docker log driver: with --log-driver=fluentd, Docker sends all Docker logs over TCP or a Unix socket to a running Fluentd process. Fluentd can then filter these logs, transform them, enrich them with additional metadata, and forward them to virtually any destination: Elasticsearch, S3, BigQuery, or another system entirely. The flexibility of Fluentd pipelines makes it possible to send the same log stream simultaneously to a fast real-time system for alerts and to a long-term archive for compliance.
Fluent Bit is the more resource-efficient alternative for environments where memory and CPU are constrained, for example on edge nodes or in large Kubernetes clusters with hundreds of pods. Fluent Bit can also read Docker logs directly from the JSON files and forward them to Loki, Elasticsearch, or Fluentd. Configuration is simpler than with Fluentd, though the transformation options are more limited. In practice, teams often run Fluent Bit as an agent on every node and Fluentd as the central aggregator that handles the complex routing logic.
8. Configuring logging correctly in your applications
The most common source of trouble with Docker logs is not the Docker configuration, it is the application itself. PHP applications write to /var/log/php/error.log by default, Nginx to /var/log/nginx/access.log and error.log, MySQL to /var/log/mysql/error.log. For Docker, these settings need to be changed so the application writes to stdout and stderr instead. With Nginx, that is a one-line configuration change: access_log /dev/stdout; and error_log /dev/stderr;. The official Docker images for Nginx, Apache, and many other services already ship with this configuration.
With PHP-FPM it gets a bit more involved: error_log = /proc/self/fd/2 in the PHP-FPM configuration redirects errors to stderr, and catch_workers_output = yes captures the output of worker processes. In Magento projects running on Docker, you also need to make sure that Magento's own logging does not write only to var/log/, but also forwards critical errors to stderr. A Monolog handler for Docker logs that configures a stream handler to php://stderr alongside the file handler is the cleanest solution.
| Log driver | Storage location | docker logs | Recommendation |
|---|---|---|---|
| json-file | Host filesystem | Yes | Development, with rotation |
| local | Host, compressed | No | Production without an external system |
| journald | systemd journal | Yes | Linux hosts running systemd |
| fluentd | External aggregator | No | Enterprise, complex pipelines |
| gelf | Graylog / external | No | Graylog infrastructure |
9. Logging strategies compared
Depending on team size, infrastructure, and compliance requirements, several logging strategies for Docker logs make sense. The right choice depends on context: a small team running a single Docker host has very different needs than a company running dozens of microservices on a Kubernetes cluster.
The most important criterion is observability: can logs be found and filtered in under a minute during a production incident? If not, the chosen strategy for Docker logs is not production ready. The second question is about persistence: logs that disappear after a container restart are worthless for post-mortem analysis. And the third question is about alerting: are there automatic notifications on error logs, without someone having to watch the logs actively?
Mironsoft
Docker logging, observability, and container infrastructure
Ready to build production-ready Docker logs?
We analyze your existing logging infrastructure, set up structured logging with the right log driver, and build a centralized log management system with Loki or Elasticsearch.
Logging audit
Analysis of your existing Docker log setup and identification of weak points
Loki setup
Building centralized log management with Grafana Loki, Promtail, and dashboards
Alerting
Setting up automatic alerts on error logs in Slack, PagerDuty, or email
10. Summary
Production-ready Docker logs come from the interplay of four layers: the application writes exclusively to stdout and stderr. The log driver forwards this output to the right destination. Rotation prevents uncontrolled disk usage. And a centralized system like Loki, Fluentd, or Elasticsearch makes logs searchable and alertable. If any one of these four layers is neglected, you will notice at the latest during the first production incident, when logs are either impossible to find, unreadable, or simply missing.
Structured logging in JSON format is the single most valuable investment in the quality of Docker logs. It costs a bit of setup effort in the application upfront, but it enables precise queries, automatic field extraction, and meaningful dashboards, all without maintaining manual log parsing rules. Container labels as automatic metadata and the right log driver for the environment round out a production-ready logging strategy.
Docker Logs: the essentials at a glance
stdout & stderr
Applications must write to stdout/stderr. Files inside the container produce no Docker logs and disappear when the container is removed.
Log rotation
Configure max-size and max-file in daemon.json or per container. Without rotation, logs consume disk space without limit.
Structured logging
JSON format with fixed fields for level, service, duration. Add container labels automatically as metadata.
Centralized log management
Loki + Promtail or Fluentd for cross-container search, persistence, and automatic alerts on critical errors.