Home DevOps Docker Monitoring and Logging: Stop Flying Blind in Production
Advanced 3 min · July 11, 2026

Docker Monitoring and Logging: Stop Flying Blind in Production

Docker monitoring and logging guide for production.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Production
production tested
July 11, 2026
last updated
258
articles · all by Naren
Before you start⏱ 35 min
  • Docker fundamentals (containers, images, docker-compose)
  • Basic Linux command line
  • Familiarity with Prometheus and Grafana concepts
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Use Prometheus with cAdvisor for container metrics, Loki for log aggregation, and Grafana for dashboards. For quick debugging, start with docker stats and docker logs --tail 100 -f .

✦ Definition~90s read
What is Docker Monitoring and Logging?

Docker monitoring and logging is the practice of collecting, aggregating, and analyzing metrics and logs from Docker containers to ensure application health, performance, and debuggability in production.

Imagine you're a landlord with 50 apartments (containers).
Plain-English First

Imagine you're a landlord with 50 apartments (containers). You need to know which apartment is flooding (high memory), which has a party (CPU spike), and who's complaining (error logs). Docker monitoring is your smart meter panel; logging is your intercom system. Without them, you're knocking on doors randomly.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You've deployed your microservices on Docker. Everything's running. Then at 3 AM, the pager goes off: 'Payment service latency > 10s.' You SSH in, run docker stats, and see one container eating 4GB of RAM. But which container? What was it doing? You have no logs, no metrics, no history. You're blind. That's the problem Docker monitoring and logging solves. By the end of this article, you'll have a production-ready stack that tells you exactly what's happening inside every container, past and present, so you can fix issues before they become incidents.

Why Default Docker Logging Will Bite You

Docker's default logging driver is json-file. It writes logs to the container's writable layer as JSON files. Sounds fine until you realize those logs live inside the container's storage, not on the host. If the container dies, logs die with it. Worse, no rotation means a chatty container can fill your disk. I've seen a single container write 20GB of logs in an hour, taking down the entire host. The fix: use a logging driver that ships logs off the container. journald for systemd integration, syslog for centralized logging, or gelf for Graylog. But the gold standard for modern stacks is the local driver: it's fast, uses binary format, and supports compression. Or use fluentd/loki for cloud-native setups. Never rely on docker logs in production — it's for debugging, not monitoring.

logging-driver-config.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
// io.thecodeforge — DevOps tutorial

// Run a container with local logging driver and rotation
docker run --log-driver local --log-opt max-size=10m --log-opt max-file=3 --name payment-service nginx

// Check current logging driver
docker info --format '{{.LoggingDriver}}'

// View logs from local driver
docker logs --tail 50 payment-service
Output
local
... (last 50 lines of log output)
Production Trap: Logging Driver Mismatch
If you change the logging driver on a running container, Docker won't apply it until you recreate the container. Always set logging driver in docker-compose or daemon.json, not per-container in production.

Container Metrics: What to Collect and Why

Docker exposes raw container metrics via the Docker API and docker stats. But docker stats is a snapshot, not a history. For real monitoring, you need time-series data. The key metrics: CPU usage (as percentage of host), memory usage (RSS + cache), network I/O (bytes in/out), block I/O (read/write), and PIDs. Why PIDs? A fork bomb in a container can exhaust the host's PID limit. I've seen a misconfigured Node.js event loop spawn thousands of child processes, freezing the host. Collect these metrics every 15s using cAdvisor, which runs as a container and exposes Prometheus endpoints. Then ship to Prometheus. Don't forget to set resource limits — without them, one container can starve others.

cadvisor-prometheus.devopsDEVOPS
1
2
3
4
5
6
7
// io.thecodeforge — DevOps tutorial

// Run cAdvisor container
docker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:ro --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --volume=/dev/disk/:/dev/disk:ro --publish=8080:8080 --detach=true --name=cadvisor gcr.io/cadvisor/cadvisor:latest

// Verify metrics endpoint
curl http://localhost:8080/metrics | head -20
Output
# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.
# TYPE cadvisor_version_info gauge
cadvisor_version_info{cadvisorRevision="...",cadvisorVersion="...",dockerVersion="...",kernelVersion="...",osVersion="..."} 1
...
Senior Shortcut: Use Docker Compose for cAdvisor
Add cAdvisor to your docker-compose.yml with image: gcr.io/cadvisor/cadvisor:latest and the same volume mounts. Pin the version to avoid breaking changes.

Prometheus: The Backbone of Container Monitoring

Prometheus scrapes metrics from cAdvisor and other exporters at configurable intervals. It's pull-based, which means you don't need to install agents — just expose an endpoint. For Docker monitoring, you'll scrape cAdvisor (container metrics), node_exporter (host metrics), and your application's custom metrics. The critical configuration: scrape interval (15s default), retention (15d default), and storage (local TSDB). In production, set retention to 30 days and use persistent volumes. Don't forget alerting rules — Prometheus can evaluate rules and fire alerts to Alertmanager. I once saw a team miss a memory leak because they only alerted on CPU. Alert on memory usage > 80% and container restarts.

prometheus-config.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — DevOps tutorial

// prometheus.yml
scrape_configs:
  - job_name: 'cadvisor'
    scrape_interval: 15s
    static_configs:
      - targets: ['cadvisor:8080']
  - job_name: 'node'
    scrape_interval: 15s
    static_configs:
      - targets: ['node-exporter:9100']

// Run Prometheus with config
docker run --volume=/path/to/prometheus.yml:/etc/prometheus/prometheus.yml --publish=9090:9090 prom/prometheus
Output
Prometheus starts and scrapes targets. Check http://localhost:9090/targets for status.
Interview Gold: Pull vs Push
Prometheus uses pull model. Why? It's simpler to secure (you control who scrapes), and it naturally handles ephemeral containers (scrape targets come and go). Push-based systems (like Graphite) require registration and can lose data if the push fails.

Grafana: Turning Metrics into Answers

Raw metrics are useless without visualization. Grafana connects to Prometheus and lets you build dashboards. For Docker monitoring, create a dashboard with: CPU usage per container (line chart), memory usage (bar chart), network I/O (area chart), and container restarts (stat panel). Use variables to filter by container name or host. The key: set up alerts in Grafana too — it can send notifications to Slack, PagerDuty, or email. Don't build one massive dashboard. Create separate dashboards for 'Container Overview', 'Host Health', and 'Application Performance'. I've seen teams spend weeks perfecting a dashboard that nobody uses. Start with the top 5 metrics that would have caught your last outage.

grafana-datasource.devopsDEVOPS
1
2
3
4
5
6
7
// io.thecodeforge — DevOps tutorial

// Run Grafana
docker run --publish=3000:3000 --volume=grafana-storage:/var/lib/grafana grafana/grafana

// Add Prometheus data source via API
curl -X POST http://admin:admin@localhost:3000/api/datasources -H 'Content-Type: application/json' -d '{"name":"Prometheus","type":"prometheus","url":"http://prometheus:9090","access":"proxy"}'
Output
{"datasource":{"id":1,"name":"Prometheus"},"message":"Datasource added"}
Never Do This: Grafana with Default Credentials
Change the admin password immediately. Use environment variables GF_SECURITY_ADMIN_USER and GF_SECURITY_ADMIN_PASSWORD. Also enable HTTPS in production.

Log Aggregation with Loki: Logs as Cheap as Metrics

Loki is Grafana's log aggregation system designed for Kubernetes and Docker. Unlike Elasticsearch, Loki doesn't index the log content — it indexes only metadata (labels like container name, host). This makes it cheaper and faster for high-volume logs. Use Promtail (or Docker plugin) to ship logs to Loki. The Docker plugin is easiest: docker plugin install grafana/loki-docker-driver:latest --alias loki. Then set --log-driver=loki on containers. Query logs in Grafana with LogQL. Example: {container_name="payment-service"} |= "error". Loki is great for 'needle in a haystack' searches. But if you need full-text search on historical logs, Elasticsearch might be better. Trade-off: cost vs capability.

loki-docker-plugin.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
// io.thecodeforge — DevOps tutorial

// Install Loki Docker driver plugin
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions

// Run Loki server
docker run --publish=3100:3100 --volume=loki-data:/loki grafana/loki:latest -config.file=/etc/loki/local-config.yaml

// Run container with Loki driver
docker run --log-driver=loki --log-opt loki-url=http://localhost:3100/loki/api/v1/push --log-opt max-size=10m --name webapp nginx

// Query logs via curl
curl -G -s 'http://localhost:3100/loki/api/v1/query_range' --data-urlencode 'query={container_name="webapp"}' | jq .
Output
{"status":"success","data":{"result":[...]}}
Production Trap: Loki Retention
Loki's default retention is 7 days. For compliance, set table_manager.retention_period: 720h (30 days) in config. Monitor disk usage — Loki compresses logs but can still grow fast.

Structured Logging: The Missing Piece

Unstructured logs are a nightmare to query. 'Payment failed at 3:02' tells you nothing. Structured logging outputs JSON with fields like timestamp, level, service, trace_id, user_id, error. This lets you filter and aggregate in Loki. In Node.js, use pino or winston. In Python, structlog. In Go, logrus or zap. The key: include a correlation ID (trace ID) that spans services. Without it, you can't trace a request across microservices. I've spent hours correlating logs manually because a team didn't include trace IDs. Use OpenTelemetry for distributed tracing — it's the industry standard now.

structured-logging.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — DevOps tutorial

const pino = require('pino');
const logger = pino({
  level: 'info',
  formatters: {
    level(label) { return { level: label }; }
  },
  timestamp: pino.stdTimeFunctions.isoTime
});

// In request handler
app.get('/pay', (req, res) => {
  const traceId = req.headers['x-trace-id'] || crypto.randomUUID();
  logger.info({ traceId, userId: req.user.id, amount: req.body.amount }, 'Payment initiated');
  // ... process payment
  logger.error({ traceId, error: err.message }, 'Payment failed');
});
Output
{"level":"info","time":"2025-03-15T10:30:00.000Z","traceId":"abc-123","userId":42,"amount":100,"msg":"Payment initiated"}
Try it live
Senior Shortcut: Log Levels in Production
Use info as default. debug only for development. warn for recoverable issues. error for failures. Never log sensitive data (passwords, PII) — even at debug level.

Alerting: Don't Wake Up for Everything

Alert fatigue is real. If every container restart triggers a page, you'll ignore the real ones. Define alerting rules in Prometheus or Grafana. Good alerts: 'Container memory > 85% for 5 minutes', 'Container restart count > 3 in 10 minutes', 'No logs from service for 5 minutes'. Bad alerts: 'CPU > 50%' (too noisy). Use severity levels: critical (page), warning (email), info (dashboard). I once worked at a company where every CPU spike paged the on-call. After a week, nobody responded. We fixed it by adding 'for' duration and only paging on sustained high usage. Also, set up a 'dead man's switch' — alert if Prometheus stops scraping.

prometheus-alert-rules.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — DevOps tutorial

// alert.rules.yml
groups:
  - name: container_alerts
    rules:
      - alert: HighMemoryUsage
        expr: (container_memory_usage_bytes{container_label_com_docker_compose_service!=""} / container_spec_memory_limit_bytes{container_label_com_docker_compose_service!=""}) > 0.85
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Container {{ $labels.container_label_com_docker_compose_service }} memory > 85%"
      - alert: ContainerRestarts
        expr: rate(container_last_seen{container_label_com_docker_compose_service!=""}[5m]) > 0.3
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Container {{ $labels.container_label_com_docker_compose_service }} restarting frequently"
Output
Prometheus loads rules. Check http://localhost:9090/rules for status.
Interview Gold: Alerting Philosophy
Alert on symptoms, not causes. 'High error rate' is a symptom. 'Database connection pool exhausted' is a cause. You want to alert on the symptom because the cause might be different next time.

When Not to Use This Stack

Prometheus + Grafana + Loki is overkill for a single container on a dev machine. For local development, docker stats and docker logs are fine. For a small app with 2-3 containers, consider a lightweight alternative like Netdata (all-in-one monitoring) or Dozzle (log viewer). Also, if you're on Kubernetes, use the native monitoring stack (kube-prometheus-stack). Don't reinvent the wheel. And if you need full-text search on logs (e.g., for compliance audits), Elasticsearch + Kibana (ELK) might be better than Loki, despite the cost. Choose based on scale and requirements, not hype.

lightweight-alternative.devopsDEVOPS
1
2
3
4
5
6
7
// io.thecodeforge — DevOps tutorial

// Run Netdata for quick monitoring
docker run --pid=host --privileged --volume=/:/host:ro,rslave --publish=19999:19999 netdata/netdata

// Run Dozzle for log viewing
docker run --volume=/var/run/docker.sock:/var/run/docker.sock --publish=8080:8080 amir20/dozzle
Output
Netdata dashboard at http://localhost:19999. Dozzle at http://localhost:8080.
Never Do This: Mix Monitoring Tools
Pick one stack and stick with it. Running cAdvisor + Netdata + Prometheus + Datadog on the same host is a recipe for resource contention and confusion.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A Node.js microservice crashed every 2 hours with exit code 137. No logs before crash.
Assumption
Team assumed a memory leak in the application code.
Root cause
The container had no memory limit set, so it used all host memory until OOM killer terminated it. The default logging driver (json-file) stored logs in the container's writable layer, which also consumed memory. No log rotation was configured.
Fix
Set --memory=512m --memory-reservation=256m on the container. Configured --log-opt max-size=10m --log-opt max-file=3 and switched to --log-driver=local for better performance. Added cAdvisor + Prometheus to track memory usage over time.
Key lesson
  • Always set memory limits and log rotation on containers.
  • Without them, you're one memory spike away from a silent OOM kill.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Prometheus target down: 'Get http://cadvisor:8080/metrics: dial tcp: connection refused'
Fix
1. Check cAdvisor container is running: docker ps | grep cadvisor. 2. Check cAdvisor logs: docker logs cadvisor --tail 20. 3. Verify network connectivity: docker exec prometheus curl http://cadvisor:8080/metrics. 4. Restart cAdvisor if needed.
Symptom · 02
Loki logs not appearing in Grafana: 'No data'
Fix
1. Check Promtail/Loki plugin is sending: docker logs <promtail-container> --tail 20. 2. Verify Loki URL in plugin config. 3. Test Loki API: curl http://loki:3100/loki/api/v1/labels. 4. Check Grafana datasource is configured correctly.
Symptom · 03
Alertmanager not firing alerts
Fix
1. Check Prometheus rules status: curl http://prometheus:9090/api/v1/rules. 2. Verify Alertmanager is running: docker ps | grep alertmanager. 3. Check Alertmanager config for correct receivers. 4. Test alert: manually fire a test alert via API.
★ Docker Monitoring and Logging Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Container exits with code 137 (OOM killed)
Immediate action
Check if OOM killer was triggered
Commands
dmesg | grep -i kill
docker inspect <container> --format '{{.State.ExitCode}}'
Fix now
Increase memory limit: docker update --memory 512m <container> or fix memory leak.
No logs from container+
Immediate action
Check logging driver
Commands
docker inspect <container> --format '{{.HostConfig.LogConfig.Type}}'
docker logs --tail 10 <container>
Fix now
If driver is 'none', recreate container with --log-driver local.
Prometheus shows 'target down' for cAdvisor+
Immediate action
Check cAdvisor container health
Commands
docker ps --filter name=cadvisor
curl http://localhost:8080/metrics | head -5
Fix now
Restart cAdvisor: docker restart cadvisor.
Grafana dashboard shows no data+
Immediate action
Check datasource connectivity
Commands
curl http://prometheus:9090/api/v1/query?query=up
Check Grafana datasource test button
Fix now
Re-add Prometheus datasource with correct URL.
Feature / AspectPrometheus + Grafana + LokiELK Stack (Elasticsearch, Logstash, Kibana)
Primary Use CaseMetrics + logs (time-series focused)Full-text search on logs
Log IndexingLabels only (no content index)Full-text index on log content
Storage CostLower (compressed, no index)Higher (indexed) per GB
Query SpeedFast for label-based queriesFast for full-text search
ComplexityModerateHigh (requires tuning)
AlertingBuilt-in (Prometheus rules)Requires Watcher or ElastAlert
ScalabilityHorizontal (microservices-friendly)Horizontal but heavy
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
logging-driver-config.devopsdocker run --log-driver local --log-opt max-size=10m --log-opt max-file=3 --name...Why Default Docker Logging Will Bite You
cadvisor-prometheus.devopsdocker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:ro --volume=/sys:/sy...Container Metrics
prometheus-config.devopsscrape_configs:Prometheus
grafana-datasource.devopsdocker run --publish=3000:3000 --volume=grafana-storage:/var/lib/grafana grafana...Grafana
loki-docker-plugin.devopsdocker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all...Log Aggregation with Loki
structured-logging.jsconst pino = require('pino');Structured Logging
prometheus-alert-rules.devopsgroups:Alerting
lightweight-alternative.devopsdocker run --pid=host --privileged --volume=/:/host:ro,rslave --publish=19999:19...When Not to Use This Stack

Key takeaways

1
Always set resource limits and log rotation on containers
without them, you're one spike away from an OOM kill or disk full.
2
Use Prometheus + cAdvisor for metrics, Loki for logs, and Grafana for dashboards
this stack is battle-tested and cost-effective.
3
Structured logging with trace IDs is non-negotiable for debugging distributed systems
without it, you're guessing.
4
Alert on symptoms, not causes
and use 'for' duration to reduce noise. Alert fatigue kills incident response.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Docker's logging driver affect container performance, and which...
Q02SENIOR
When would you choose Loki over Elasticsearch for log aggregation in a D...
Q03SENIOR
What happens when a container's logging driver is set to 'none' and you ...
Q04JUNIOR
Explain the difference between `docker stats` and Prometheus/cAdvisor fo...
Q05SENIOR
You see a container restarting every 5 minutes with exit code 139. Walk ...
Q06SENIOR
How would you design a monitoring and logging system for a 50-microservi...
Q01 of 06SENIOR

How does Docker's logging driver affect container performance, and which driver would you choose for high-throughput production services?

ANSWER
The json-file driver writes JSON to the container's writable layer, causing I/O contention and storage bloat. For high throughput, use local driver (binary, compressed) or loki (streams directly to Loki). Avoid syslog as it can block if the syslog server is slow.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I monitor Docker containers in production?
02
What's the difference between Docker's json-file and local logging drivers?
03
How do I view logs from a stopped container?
04
How do I set up Prometheus to monitor Docker containers?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 11, 2026
last updated
258
articles · all by Naren
🔥

That's Docker. Mark it forged?

3 min read · try the examples if you haven't

Previous
Docker Image Security Scanning
27 / 32 · Docker
Next
Docker Resource Limits and cgroups