5 Metrics to Monitor for Smooth Docker Container Performance

A practical guide to watching CPU, memory, disk, network, and logs for your Docker containers, so problems surface before your users find them.
Why collect metrics about your container?
Containers fail quietly. A memory leak, a runaway process, or a disk filling up rarely announces itself; it just makes things slow, then makes things break. Collecting metrics turns that guesswork into evidence. With the right numbers in front of you, you can catch a resource problem while it is still a warning sign, right-size the resources a container actually needs, and prove whether a slowdown is your app, your host, or something in between.
Below are the five signals worth watching, what each one tells you, how to read it, and roughly when to worry.
The five signals
CPU usage — is the container doing more work than it should?
Memory usage — is it leaking, or about to get killed?
Disk usage — is it filling up the host?
Network usage — is traffic normal, or is something chatty?
Logs — what actually happened, in order?
CPU usage
CPU usage tells you how much processor time your container is consuming. The quickest look is a live stream:
docker statsThat runs like top: it holds the terminal and refreshes until you Ctrl-C, at which point the numbers clear. For a single snapshot that prints once and hands the prompt back, and to target one container by name, add --no-stream:
docker stats --no-stream <container-name>The CPU % column is relative to the host's total capacity, so on an 8-core machine a single container can legitimately read up to 800%. That is why a raw number means little without context. What matters is the pattern: a container that idles at 5% and spikes to 200% during a job is healthy; one that sits pinned near its ceiling is not.
If a container consistently maxes out, either it needs a bigger allocation or something is wrong (a hot loop, an unindexed query, a retry storm). Cap it so one container cannot starve the others. Set the limit where the container is defined. In Compose:
services:
app:
cpus: 1.5Then docker compose up -d to apply. To cap a container that is already running, without recreating it, use docker update:
docker update --cpus="1.5" <container-name>A bare docker run --cpus="1.5" my-image also sets the limit, but only when you are launching a fresh container, not adjusting one that is already up.
Rule of thumb: sustained usage above roughly 80% of a container's CPU limit is your signal to investigate or scale.
Memory usage
Memory is the metric most likely to kill a container outright. Watch the MEM USAGE / LIMIT and MEM % columns in docker stats. The number to fear is a slow, steady climb that never comes back down under load: that is the classic shape of a memory leak.
Set a hard limit so a leaking container takes itself down instead of the whole host. In Compose:
services:
app:
mem_limit: 512mOr on a container already running: docker update --memory="512m" <container-name>. One caveat: docker update --memory can fail on hosts without kernel swap-limit accounting; if it errors, set the limit in Compose and recreate the container.
When a container exceeds its memory limit, Docker's OOM killer stops it and you will see exit code 137. Seeing 137 in your restart logs is a direct signal that the limit is too low or the app is leaking.
Rule of thumb: if memory usage trends upward across restarts and never plateaus, suspect a leak before you suspect the limit.
Disk usage
This is the one people get wrong, so read carefully: docker stats does not show how much storage a container is using. Its BLOCK I/O column is disk read/write throughput, not consumed space. To see actual storage, use different commands.
For per-container size (the writable layer):
docker ps -sFor the full picture across images, containers, volumes, and build cache:
docker system dfAdd -v for a verbose, per-object breakdown. The usual culprits behind a host that mysteriously runs out of space are unbounded log files, dangling images, and orphaned volumes. When space gets tight, docker system prune (used deliberately) reclaims it.
Rule of thumb: if writable-layer size grows unbounded, your app is writing data inside the container that belongs in a volume or an external store.
Network usage
Network usage measures the traffic in and out of your container. The NET I/O column in docker stats shows cumulative received and transmitted bytes. This one is best read as a baseline: learn what normal looks like, then watch for departures from it. A container that suddenly transmits far more than usual might be under load, retrying against a failing dependency, or exfiltrating data if something is compromised.
Rule of thumb: alert on deviation from the baseline, not on an absolute number, since "normal" depends entirely on what the service does.
Logs
Logs are not a metric, they are the event record that explains what the metrics were reacting to. When CPU spiked at 3 a.m., the logs tell you why. Read them with:
docker logs --tail 100 -f my-containerTwo things worth configuring rather than ignoring. First, logs grow: without rotation, a chatty container can fill the disk (see the disk section above). Cap them in your Compose file:
services:
app:
logging:
options:
max-size: "10m"
max-file: "3"(The docker run equivalent is --log-opt max-size=10m --log-opt max-file=3, again only at launch.)
Second, docker logs only works with the json-file or journald drivers. If you ship logs to an external system, plan to read them there instead.
Going beyond spot-checks
Everything above is a spot-check. docker stats shows you now; it does not remember, chart, or alert. Real monitoring means continuous collection plus a way to be told when something crosses a line. The standard open-source stack:
cAdvisor exposes per-container CPU, memory, disk, and network metrics from the same underlying cgroup data
docker statsreads.Prometheus scrapes and stores those metrics over time so you can see trends, not just snapshots.
Grafana turns them into dashboards, and Prometheus Alertmanager pages you when a threshold is breached.
The spot-check tools are perfect for debugging a problem you already know about. The stack is what tells you about the problem before you know about it.
Conclusion
Monitoring a Docker container comes down to five signals: CPU and memory tell you how hard it is working and whether it is about to die, disk and network tell you what it is consuming, and logs tell you why any of it happened. Use docker stats, docker ps -s, docker system df, and docker logs to check in the moment, then graduate to cAdvisor, Prometheus, and Grafana once you want to stop watching manually. Get those in place and container problems become something you get ahead of instead of something you chase.