skip to content

How do you check what CPU and memory running Docker containers are using right now, and what does each column of the `docker stats` output mean?

level: juniorimportance: should knowfreq 50%

answer

  1. stats = cgroup counters, formatted
  2. CPU % sums cores: 400% = 4 cores
  3. no --memory => LIMIT shows host RAM
  4. --no-stream + --format for scripts
  5. throttling hides in cpu.stat, not CPU %

basics

~20 s

Run docker stats. It live-streams per-container CPU % (summed across cores, so it can exceed 100%), MEM USAGE / LIMIT and MEM %, NET I/O, BLOCK I/O and PIDS, read from each container's kernel cgroup counters. Add --no-stream for a single snapshot.

solid answer

~50 s

`docker stats` shows a live table of per-container usage taken from the cgroup counters the kernel keeps for each container. - **CPU %** is CPU time relative to one core, summed across cores, so 250% means two and a half cores' worth. With a CPU limit set, that limit is the ceiling. - **MEM USAGE / LIMIT** is charged memory and the effective limit; with no `--memory` flag the limit shown is host RAM. Docker subtracts reclaimable page cache, so usage tracks real footprint reasonably well. - **MEM %** is usage over that limit. - **NET I/O** and **BLOCK I/O** are cumulative bytes since container start, not rates. - **PIDS** counts processes and threads. `--no-stream` gives one snapshot for scripts, `--format` picks fields, and naming containers narrows it. Inside a container you can read the cgroup files directly (`/sys/fs/cgroup/memory.max`, `memory.current`). It is a spot check, not a monitoring system.

code

bash · 1 line
bash
docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.PIDs}}'

go deeper

for a junior

Know the command, the --no-stream flag, and what each column measures; be able to say CPU % can exceed 100%.

for a middle

Explain that the numbers are cgroup counters, that usage excludes reclaimable cache, and that the LIMIT column falls back to host RAM when no limit is set.

for a senior

Point out sampling blind spots, note that throttling must be read from cpu.stat, and connect observed peaks to the limits you then set.

for a principal

Frame ad-hoc stats as triage only, and describe how per-host measurement feeds a fleet-wide capacity and limit-setting policy.

## What the command is `docker stats` is the built-in, zero-setup way to see what running containers consume right now. By default it attaches to every running container and repaints the table about once a second until you press Ctrl-C. Pass names or IDs to narrow it, `--no-stream` to print one sample and exit, and `--format` to emit chosen fields for scripts. ## Where the numbers come from Docker does not measure anything itself. Every container runs inside a control group (cgroup) — the kernel mechanism that accounts for and limits the resources of a set of processes. The daemon reads that cgroup's counters (`cpu.stat`, `memory.current`, `memory.max`, `io.stat` on cgroup v2) and formats them. You can read the same files yourself from inside the container under `/sys/fs/cgroup`, which is handy in an image that has no Docker CLI. ## Reading each column **CPU %** comes from the delta of consumed CPU time between two samples divided by elapsed wall-clock time, expressed against a single core. A single-threaded process pegged flat shows about 100%; a process using four cores shows about 400%. That surprises people who expect 100% to be the maximum. If the container has a hard CPU cap, the value cannot exceed it — a container run with `--cpus=1.5` tops out near 150%. **MEM USAGE / LIMIT** shows charged memory and the limit in force. If you never passed `--memory`, there is no container limit and Docker displays total host RAM, which makes MEM % look reassuringly small even when the container is the biggest consumer on the box. The usage figure excludes reclaimable file cache (inactive file pages), because otherwise every container that reads large files would look near its limit; the kernel can drop that cache under pressure, so counting it would mislead. **NET I/O** and **BLOCK I/O** are cumulative totals since the container started, not rates — to get a rate you diff two samples. Block I/O only counts traffic that reached the block device, so writes absorbed by page cache appear later or not at all. **PIDS** is the count of processes and threads. It is the column that catches thread leaks, and it pairs with `--pids-limit`, which caps that number so a fork bomb fails instead of taking the host down. ## Gotchas worth saying out loud - Stats are sampled, so a short spike between samples is invisible. A container killed for a one-second allocation burst can show a comfortable average right up to its death. - Only running containers appear by default; `-a` includes stopped ones, which report zeros. - On a busy host, streaming stats for every container costs measurable daemon CPU; prefer `--no-stream` plus explicit names in scripts. - `docker stats` tells you usage, not whether the container is being throttled. CPU throttling shows up in the cgroup's `cpu.stat` as `nr_throttled` and `throttled_usec`, never in the CPU % column — a throttled container can sit exactly at its cap, look healthy, and still have terrible latency. - Memory usage shown here is not the same as the number a language runtime reports internally; the cgroup counts everything the process touches, including native allocations the runtime does not track. ## How it fits with limits The practical loop is: run the workload under realistic load, watch `docker stats` and the raw cgroup counters to learn steady-state and peak usage, then set `--memory` and `--cpus` with headroom above the observed peak. Without limits, one container can consume the whole host; with limits guessed rather than measured, you either waste capacity or get containers killed. Continuous, historical measurement across a fleet is a monitoring-stack job — `docker stats` is what you reach for on one host, during triage, right now.

  • Why can the CPU % column read 400%?
    The percentage is CPU time relative to a single core, summed over every core the container's threads ran on. A four-threaded process saturating four cores reports about 400%. A hard cap set with `--cpus` bounds the value; without a cap the ceiling is host core count times 100%.
  • MEM % says 3% but the host is swapping. What is wrong?
    Almost certainly no `--memory` limit was set, so the LIMIT column is total host RAM and the percentage is measured against the whole machine rather than any container budget. Set explicit limits so the number means something, and meanwhile compare absolute MEM USAGE against host RAM minus what other containers and the OS need.

saying these in an interview costs you the question

  • Believing CPU % is capped at 100%
  • Reading MEM % as meaningful when no --memory limit was ever set
  • Treating NET I/O and BLOCK I/O as rates rather than cumulative totals
  • Assuming normal-looking docker stats proves the container is not CPU-throttled
  • Using streaming docker stats as the production monitoring solution

context