Learn how to track Docker container CPU and memory usage with built-in tools and practical commands — no dedicated SRE required.
Your app slows to a crawl at 2 a.m., and you have no idea which container is eating all the CPU. By morning the problem has vanished, but you’re left with zero evidence and a vague sense of dread. If you’re running Docker in production without dedicated monitoring, that scenario is only a matter of time. The good news: Docker exposes a surprising amount of resource data out of the box — you just need to know where to look.
The Quickest Starting Point: docker stats
Docker ships with a live stats command that works immediately, no extra software required. Run it and you get a continuously refreshing table of every running container:
- docker stats — streams live CPU %, memory usage vs. limit, network I/O, and block I/O for all running containers
- docker stats — scope it to one container
- docker stats –no-stream — prints a single snapshot and exits, useful in scripts or cron jobs
- docker stats –format ‘table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}’ — trim the output to only the columns you care about The CPU percentage shown is relative to a single core. On a 4-core host, 100% means one full core is saturated. Keep that in mind when reading numbers above 100% — your container is consuming more than one core, not breaking mathematics.
Reading the Memory Numbers Correctly
Memory output looks like ‘210MiB / 1.95GiB’. The first number is the container’s current RSS-style usage; the second is its limit (or the host’s total RAM if no limit is set). A container with no memory limit will happily consume all available RAM before the kernel OOM-killer starts terminating processes — usually the worst possible time to discover this.
Always set memory limits on production containers. Without them, a single runaway process can starve every other service on the host.
Set a hard limit at run time with –memory and a soft warning threshold with –memory-reservation:
- docker run –memory=’512m’ –memory-reservation=’400m’ your-image — container gets a hard 512 MB ceiling
- docker inspect | grep -i memory — verify limits on an already-running container
- cat /sys/fs/cgroup/memory/docker//memory.usage_in_bytes — read raw cgroup data directly if you need it in a script
Capturing Historical Data with docker stats and Simple Scripts
docker stats is great for real-time debugging but terrible for answering ‘what was happening at 2 a.m.?’ For historical data without standing up a full monitoring stack, a small shell loop written to a log file is often enough to start:
- while true; do docker stats –no-stream –format ‘{{.Name}},{{.CPUPerc}},{{.MemUsage}}’ >> /var/log/container-stats.csv; sleep 30; done — logs a CSV snapshot every 30 seconds
- Run it inside a tmux or screen session, or as a systemd service, so it survives SSH disconnects
- Pipe output to grep or awk to filter for specific containers in high-container-count environments This is not a replacement for proper time-series storage, but it gives you a simple audit trail you can grep through when something goes wrong.
Going Deeper: cAdvisor and Prometheus
If your team is ready for a more complete solution, Google’s cAdvisor (Container Advisor) is the standard open-source exporter for Docker metrics. It runs as a container itself, scrapes cgroup data, and exposes a Prometheus-compatible endpoint:
- docker run –volume=/var/run:/var/run:ro –volume=/sys:/sys:ro –volume=/var/lib/docker/:/var/lib/docker:ro –publish=8080:8080 –detach=true –name=cadvisor gcr.io/cadvisor/cadvisor — starts cAdvisor with read-only host access
- Browse http://localhost:8080/containers/ for a built-in web UI with CPU and memory graphs
- Scrape http://localhost:8080/metrics from Prometheus for long-term storage and alerting
- Key metrics to watch: container_cpu_usage_seconds_total, container_memory_usage_bytes, container_memory_working_set_bytes (working set is usually more meaningful than raw usage)
Pair cAdvisor with Grafana and you have a full dashboard. The setup takes an afternoon but pays for itself the first time you need to correlate a deploy with a memory spike hours later.
What to Actually Alert On
Collecting data is only half the job — you need to know when to wake someone up. Focus on these thresholds as a practical starting point:
- CPU sustained above 80% for more than 5 minutes — a brief spike is normal; sustained high CPU usually signals a stuck process or traffic anomaly
- Memory usage above 90% of the container limit — at this point OOM-kills are close; investigate before the kernel does it for you
- Memory usage growing steadily over hours with no plateau — classic memory leak signature
- Container restart count increasing — docker inspect | grep RestartCount will show you; repeated restarts mean something is crashing silently If managing these thresholds across multiple containers sounds tedious, that’s exactly the gap tools like Opservo are built to fill — it watches your containers continuously, surfaces anomalies in plain English, and flags the signals that actually matter before they become incidents. For small teams without a dedicated ops person, that kind of automated context can be the difference between catching a problem at 9 a.m. and getting paged at 2 a.m.
Start with docker stats today. Add cgroup limits to every production container this week. When you’re ready for historical data and intelligent alerting, build from there — the foundation you lay now makes every future improvement much easier.
Originally published on the Opservo blog — Opservo is the AI ops engineer for teams without an SRE. Free for 2 servers → https://getopservo.com/welcome