Container Logging and Monitoring¶
Overview¶
Containers are ephemeral, but the signals they emit — stdout/stderr logs, exit codes, CPU and memory usage — must be durable and observable for debugging, SLO tracking, and incident response. Without a logging and monitoring strategy, docker logs on a single host does not scale to fleets of nodes, and deleted containers take their last hour of stderr with them.
This tutorial covers Docker's logging drivers, practical log rotation, reading logs with CLI tools, real-time resource metrics with docker stats, and a preview of cAdvisor for per-container CPU, memory, and network metrics on a host. These foundations transfer directly to Kubernetes (Fluent Bit, Prometheus, Grafana) and cloud-managed container services.
This is Tutorial 13 in Module 5: Operations of the REBASH Academy Docker series. Complete Environment Variables and Secrets before starting Module 5.
Prerequisites¶
- Docker Engine on Linux (logging driver behavior differs slightly on Docker Desktop)
- Completion of Running Your First Container
- Familiarity with Log Management with journalctl concepts
- Optional: second terminal window to generate load while observing stats
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Explain how container logs flow from application stdout to storage backends
- Configure the json-file logging driver with rotation limits
- Retrieve, follow, and filter logs with
docker logs - Monitor live CPU, memory, and I/O with
docker stats - Run cAdvisor and interpret basic container resource dashboards
- Describe production logging patterns (centralized aggregation, structured JSON logs)
Architecture Diagram¶
Application processes write to stdout/stderr. The container runtime captures streams and forwards them through the configured logging driver.
flowchart LR
APP["Application<br/>stdout / stderr"]
ENGINE["Docker Engine<br/>logging driver"]
LOCAL[json-file on disk]
SYSLOG[syslog]
FLUENT["Fluentd / Fluent Bit"]
CLOUD["CloudWatch / Stackdriver"]
ELK["Elasticsearch / Loki"]
APP --> ENGINE
ENGINE --> LOCAL
ENGINE --> SYSLOG
ENGINE --> FLUENT
FLUENT --> ELK
FLUENT --> CLOUD```
## Theory
### Container Logs Are Stream Captures
Unlike traditional apps writing to `/var/log/app.log` inside the container, **Twelve-Factor** and cloud-native practice recommend logging to **stdout/stderr**. Docker's containerd/shim captures these streams and hands them to a **logging driver**.
Benefits:
- No volume management for log files inside containers
- Same pattern on Docker, Kubernetes, Cloud Run, and Lambda
- Log shippers attach at the host or cluster level
Trade-off: extremely high-volume debug logging can fill disk if rotation is misconfigured.
### Logging Drivers
Docker supports multiple drivers via `/etc/docker/daemon.json` (default) or per-container `--log-driver`.
| Driver | Default? | Typical use |
|--------|----------|-------------|
| **json-file** | Yes (Linux) | Local files under `/var/lib/docker/containers/` |
| **local** | Alternative local | Optimized binary format, faster rotation |
| **syslog** | No | Forward to syslog daemon |
| **journald** | No | Integrate with systemd journal on host |
| **fluentd** / **gelf** | No | Forward to centralized collectors |
| **awslogs** / **gcplogs** | No | Native cloud log groups (requires plugins/setup) |
| **none** | No | Discard logs (batch jobs with no observability need) |
Check active driver:
```bash
docker info | grep 'Logging Driver' Per-container override example:
json-file Rotation¶
Unbounded json-file logs are a top cause of disk-full incidents on Docker hosts. Configure defaults in /etc/docker/daemon.json:
Each container log rotates at 10 MB, keeping three files (~30 MB cap per container). Restart Docker after daemon changes. Existing containers keep their creation-time driver options until recreated.
docker logs Command¶
docker logs reads from the configured driver (for json-file/local, from disk):
| Flag | Purpose |
|---|---|
-f | Follow (like tail -f) |
--tail N | Show last N lines |
--since | Logs since timestamp or duration |
--until | Logs before timestamp |
-t | Show timestamps |
Logs include both stdout and stderr unless separated by driver features. Exit code and OOM events appear in docker inspect, not always in application logs.
Structured Logging¶
Plain text logs are hard to query at scale. Production apps emit JSON lines:
Central systems (Elasticsearch, Loki, CloudWatch Logs Insights) index fields. Docker does not parse JSON — the collector downstream does. Include request_id, service, and version fields for correlation.
docker stats — Live Resource Metrics¶
docker stats streams container cgroup metrics:
- CPU % (relative to host cores)
- Memory usage / limit
- Network I/O
- Block I/O
Single snapshot:
Limit to specific containers:
Limitations: Host-level view only; no historical retention; resets when container restarts. Production uses Prometheus, Datadog, or cloud monitoring for time-series storage.
cAdvisor Preview¶
cAdvisor (Container Advisor) is a Google open-source tool that exposes per-container resource usage and historical graphs via a web UI. It reads cgroup and filesystem metrics from the host.
Typical deployment: cAdvisor runs as a container with host mounts:
/:/rootfs:ro/var/run:/var/run:ro/sys:/sys:ro/var/lib/docker/:/var/lib/docker:ro
Default UI port: 8080. In production, Prometheus scrapes cAdvisor's /metrics endpoint; Grafana dashboards visualize pod/container usage. Kubernetes users often rely on kubelet/cAdvisor integration built into nodes.
cAdvisor complements docker stats with graphs and exportable metrics — it does not replace centralized logging.
Production Observability Stack (Conceptual)¶
| Layer | Docker-era tools | Kubernetes-era evolution |
|---|---|---|
| Logs | json-file + Fluent Bit → Loki/ELK | DaemonSet log agents |
| Metrics | cAdvisor + Prometheus + Grafana | kube-prometheus-stack |
| Traces | OpenTelemetry SDK → Jaeger/Tempo | Same, sidecar or eBPF |
| Alerts | Alertmanager, PagerDuty | SLO-based alerting on RED metrics |
RED method for services: Rate (requests/sec), Errors, Duration — derived from logs and metrics together.
Hands-on Lab¶
Step 1 – Create a verbose logging container¶
Command:
docker run -d --name log-lab \
alpine sh -c 'i=0; while true; do i=$((i+1)); echo "tick number $i"; sleep 2; done'
sleep 3
docker logs log-lab --tail 5
Explanation: The container writes numbered lines to stdout every two seconds — simulates application logging.
Expected output:
Step 2 – Follow logs in real time¶
Open a second terminal for this step, or run with a timeout.
Command:
Explanation: -f follows new output; -t prefixes ISO8601 timestamps from Docker (not necessarily from the app).
Step 3 – Filter logs by time¶
Command:
Explanation: --since accepts durations (5s, 2m) or RFC3339 timestamps — useful during incident triage.
Step 4 – Inspect logging driver configuration¶
Command:
Explanation: Per-container LogConfig shows driver and options. Compare with daemon-wide defaults from docker info.
Step 5 – Run a container with log rotation options¶
Command:
docker run -d --name log-lab-rot \
--log-driver=json-file \
--log-opt max-size=1m \
--log-opt max-file=2 \
alpine sh -c 'while true; do dd if=/dev/zero bs=1024 count=50 2>/dev/null | base64; sleep 1; done'
sleep 15
docker inspect log-lab-rot | grep -A8 '"LogConfig"'
docker rm -f log-lab-rot
Explanation: High-volume output triggers rotation at 1 MB with two file retention. Prevents one chatty container from filling the disk.
Step 6 – Monitor with docker stats¶
Command:
Explanation: Memory limit appears in stats output. CPU percentage is relative to one full host core unless limits apply.
Expected output (columns vary by version):
Generate load optional:
docker exec stats-lab sh -c 'while true; do :; done' &
sleep 3
docker stats stats-lab --no-stream
docker rm -f stats-lab
Step 7 – Run cAdvisor (preview)¶
Command:
docker run -d \
--name=cadvisor \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \
--detach=true \
gcr.io/cadvisor/cadvisor:v0.49.1
sleep 5
curl -s -o /dev/null -w 'cAdvisor HTTP %{http_code}\n' http://127.0.0.1:8080/containers/
Explanation: Open http://127.0.0.1:8080 in a browser to explore per-container graphs. Pin a specific cAdvisor version in production; latest tags drift.
Expected output:
Step 8 – cAdvisor metrics endpoint¶
Command:
Explanation: Prometheus scrapes this endpoint. Metric names include container_cpu_usage_seconds_total, container_memory_usage_bytes, and labels for container name/id.
Step 9 – Clean up¶
Command:
Commands & Code¶
| Command | Description | Example |
|---|---|---|
docker logs | Fetch container logs | docker logs -f --tail 100 web |
docker events | Stream Docker daemon events | docker events --filter container=web |
docker stats | Live resource usage | docker stats --no-stream |
docker inspect | Log config and state | docker inspect web |
docker info | Daemon logging defaults | docker info |
daemon.json log rotation template¶
Apply on Linux hosts running many containers:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5",
"compress": "true"
}
}
Restart Docker: sudo systemctl restart docker — plan maintenance; recreates may be needed for opts on existing containers.
Minimal Fluent Bit forward (conceptual)¶
Production hosts often ship json-file logs via Fluent Bit. Illustrative /fluent-bit/etc/fluent-bit.conf fragment:
[INPUT]
Name tail
Path /var/lib/docker/containers/*/*-json.log
Parser docker
Tag docker.*
[OUTPUT]
Name loki
Match docker.*
Host loki.example.com
Port 3100
Exact paths depend on your log driver and distribution.
Common Mistakes¶
Logging inside the container filesystem only
Logs written only to /var/log/app.log inside the container are lost on docker rm unless mounted. Prefer stdout or mount a volume to the host.
No log rotation on json-file driver
Default infinite growth fills /var/lib/docker. Always set max-size and max-file in daemon.json or per container.
Treating docker stats as historical monitoring
Stats are point-in-time/live only. Incidents requiring "memory usage last Tuesday" need Prometheus or cloud metrics.
Exposing cAdvisor without authentication
cAdvisor reveals container metadata and resource data. Bind to localhost or protect with network policy and auth in production.
Best Practices¶
Log to stdout in JSON for production services
Structured logs enable high-cardinality search without regex parsing fragile plain text.
Set memory and CPU limits before interpreting stats
Unbounded containers can starve the host; stats show usage relative to limits when configured.
Correlate logs with container IDs and image digests
Include container_id and image_tag in deploy logs — maps observability back to registry artifacts from Tutorial 11.
Plan centralized logging before fleet scale
json-file on each host works for tens of containers; hundreds require Fluent Bit, Datadog agent, or cloud log drivers.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
docker logs empty | App logs to file not stdout | Fix app or tail mounted volume |
Disk full on /var/lib/docker | Log rotation missing | Add max-size/max-file; prune unused containers |
docker logs -f hangs silently | App buffers stdout | Run Python with PYTHONUNBUFFERED=1; flush logs in app |
| stats show 0 CPU always | Idle container | Generate load; verify container running |
| cAdvisor UI empty | Wrong mounts or permissions | Verify volume paths; run on Linux host |
| Logs missing after restart | Driver none or remote-only | Check LogConfig; verify remote collector health |
| Timestamps inconsistent | App vs Docker timestamps | Use -t on docker logs; emit ISO8601 in app JSON |
Summary¶
- Container logs capture stdout/stderr through logging drivers — default
json-filestores JSON lines on the host - Configure log rotation (
max-size,max-file) to prevent disk exhaustion docker logsretrieves and filters historical output;docker statsshows live CPU/memory/network- cAdvisor exposes per-container metrics and a web UI; Prometheus scrapes
/metricsin production - Mature observability combines centralized logs, metrics, and traces — Docker CLI tools are the starting point, not the destination
Interview Questions¶
- Where do container logs go when an app prints to stdout?
- How do you prevent Docker json-file logs from filling the disk?
- What is the difference between docker logs and application log files inside the container?
- What metrics does docker stats provide, and what are its limitations?
- What is cAdvisor, and how does it relate to Prometheus?
- Why is structured JSON logging recommended for microservices?
- How would you forward Docker logs to Elasticsearch or CloudWatch?
- What logging driver would you use to discard logs entirely, and when?
- How do cgroup memory limits appear in docker stats output?
- What is the RED method, and which Docker tools help measure its components?
Sample Answers (Questions 1, 4, and 5)
Q1 — stdout destination: The container runtime attaches to the process stdout/stderr file descriptors. Docker's configured logging driver (default json-file) receives each line and persists or forwards it — typically to /var/lib/docker/containers/<id>/<id>-json.log on the host for json-file.
Q4 — docker stats: Provides live CPU%, memory usage/limit, network I/O, and block I/O per container from cgroup statistics. Limitations: no history, host-scoped only, resets on container restart, and CPU% interpretation depends on host core count and limits.
Q5 — cAdvisor: Container Advisor collects resource usage and performance characteristics per container from cgroups and exposes them via web UI and Prometheus metrics. Prometheus scrapes /metrics for time-series storage; Grafana visualizes trends — unlike docker stats, enabling alerting and capacity planning.
Related Tutorials¶
- Docker – Category Overview
- Environment Variables and Secrets (end of Module 4)
- Docker Security Hardening (next in Module 5)
- Log Management with journalctl
- Monitoring – Category Overview