Docker Logging Drivers#
Docker supports multiple logging mechanisms ("logging drivers") that determine where and how container output is sent. Each Docker daemon has a default logging driver; every container uses it unless overridden with --log-driver .
Built-in drivers at a glance#
| Driver | Where logs go |
|---|---|
json-file (default) | JSON files on the host |
local | Compact files on the host, with rotation |
gelf | GELF endpoint (Graylog, Logstash, Fluentd) |
syslog | Host syslog daemon |
fluentd | Fluentd daemon on the host |
splunk | Splunk HTTP Event Collector |
journald | systemd journald |
awslogs / gcplogs | Cloud logging services |
The default json-file driver performs no log rotation; local is recommended for production to avoid disk exhaustion .
Configuring a driver#
Daemon-wide default (/etc/docker/daemon.json):
{
"log-driver": "gelf",
"log-opts": { "gelf-address": "udp://1.2.3.4:12201" }
}
Restart Docker for the change to apply to new containers. Existing containers are unaffected .
Per-container override:
docker run --log-driver gelf --log-opt gelf-address=udp://1.2.3.4:12201 alpine
Dual logging (keeping docker logs usable)#
Remote drivers like gelf and splunk don't support reading logs back locally, so docker logs would fail. Docker Engine's dual logging feature transparently caches a copy via the local driver so docker logs keeps working . Dual logging is enabled by default; it can be disabled per-container with cache-disabled: "true" when storage is a concern .
GELF driver specifics#
GELF is understood by Graylog, Logstash, and Fluentd. Every message carries: version, host, timestamp, short/long message text, and any custom fields . The required option is gelf-address (udp:// or tcp:// with port). UDP uses gzip compression by default β set gelf-compression-type=none to reduce CPU overhead .
The Critical Networking Pitfall: Daemon Namespace vs. Container Namespace#
Remote logging drivers run in the Docker daemon's process, not inside the container. The daemon lives on the host network namespace. This means the address you put in gelf-address (or the equivalent option for syslog/Fluentd/Splunk) must be reachable from the host, not from the container's network namespace.
This has a counter-intuitive consequence in Docker Compose environments:
| Address in log option | What it actually resolves to | Result |
|---|---|---|
udp://localhost:12201 | Host loopback (not the logstash container) | β No listener β container fails to start |
udp://127.0.0.1:12201 | Same as above | β Same failure |
udp://logstash:12201 | Docker's embedded DNS (Linux daemon) | β Works on Linux; β οΈ may differ on Docker Desktop |
udp://<host-IP>:12201 | Explicit host IP | β Reliably reachable from daemon |
Rule:
localhostin a logging-driver address is the host loopback, not any container. On Linux, Compose service names do resolve from the daemon via Docker's embedded DNS. On Docker Desktop (Mac/Windows) the daemon runs inside a VM, so service-name resolution from the daemon may require extra configuration.
The course material in reseaux.adoc documents this as one of the most common and costly exam errors: a student configuring GELF logging toward localhost expecting it to reach a logstash sibling container. The COURSE_IMPROVEMENTS_DOCKER_NETWORKING.adoc file elaborates:
"NGINX tries to send logs to
localhost:12201= itself. Logstash listens on its ownlocalhost:12201= unreachable from NGINX. Result: NGINX crashes because it cannot connect."
Broken:
services:
backend:
logging:
driver: gelf
options:
gelf-address: "udp://localhost:12201" # β points to the container, not logstash
logstash:
ports:
- "12201:12201/udp"
Fixed:
services:
backend:
logging:
driver: gelf
options:
gelf-address: "udp://logstash:12201" # β
service name
Localhost vs. Service Names in Docker Compose β Full Reference#
The localhost confusion is not unique to logging drivers β it is a general Docker networking principle . Logging drivers make it especially hazardous because failures are often silent or appear only as mysterious container startup crashes.
Mental model :
localhostinside a container = that container only- Reaching another container = use the Compose service name
- Reaching from a browser = use a reverse proxy or public URL
Where localhost is correct inside a container#
Health checks test the container's own process, so localhost (or 127.0.0.1) is correct there :
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
Quick reference table#
| Situation | Use | Example |
|---|---|---|
| Health check (same container) | localhost | http://localhost:8080/health |
| Inter-service communication | service name | http://backend:5000 |
| GELF logging to Logstash | service name | udp://logstash:12201 |
| DB connection string | service name | postgresql://db:5432/mydb |
| Frontend β backend (browser) | reverse proxy | /api via NGINX |
Docker's internal DNS#
User-defined networks (created with docker network create or via Compose) provide an embedded DNS at 127.0.0.11 that resolves service names . The default bridge network does not have this DNS β service name resolution requires a custom or Compose-managed network .