Docker Logs: stdout, stderr, and Production-Ready Logging Strategies
AI generated
Docker · Logging · DevOps · Monitoring
Docker Logs: stdout, stderr
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.

12 min read stdout · stderr · log drivers · Loki · Fluentd · JSON Docker 24+ · Linux · Production

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.

11. FAQ: Docker Logs and Logging Strategies

1Why is my application not producing any Docker logs?
The application is writing to files instead of stdout/stderr. Nginx, PHP-FPM, and other services need explicit configuration. Official Docker images usually already do this.
2How do I stop logs from filling up disk space?
Configure max-size and max-file in daemon.json or per container. The local driver rotates automatically and compresses the logs.
3Difference between local and json-file log drivers?
json-file: readable JSON lines, docker logs available. local: compressed binary format, automatic rotation, but not queryable through the Docker API.
4Why is docker logs not sufficient in production?
No cross-container search, no alerting, no persistence beyond container removal. A centralized log management system is required for production.
5Which log driver is best for production?
Small teams: json-file with rotation. Loki setup: json-file with Promtail. Enterprise: fluentd or gelf. local as a compromise without an external system.
6What is structured logging?
Logs in JSON format with fixed fields like level, service, timestamp. Enables precise queries and automatic field extraction without regular expressions.
7How does Promtail work for Docker logs?
Promtail reads Docker log files from /var/lib/docker/containers/, enriches them with labels, and sends them to Loki. docker_sd_configs handles automatic container discovery.
8Fluentd vs. Fluent Bit: what is the difference?
Fluentd: full aggregator with an extensive plugin ecosystem. Fluent Bit: lightweight agent with a small memory footprint. Often combined.
9How do I configure PHP-FPM for Docker logs?
error_log = /proc/self/fd/2 redirects errors to stderr. catch_workers_output = yes captures worker output. The Dockerfile must override the FPM configuration accordingly.
10Are Docker logs deleted on container restart?
No, logs are preserved on restart. They are deleted when the container is removed (docker rm). A centralized log system prevents data loss.