skip to content

What do the columns of `docker stats` show for a running container, and where do those numbers come from?

level: juniorimportance: should knowfreq 60%

answer

  1. One line per running container, refreshed live
  2. The daemon reads it from the host
  3. Cgroup counters, not tools inside the container
  4. CPU % is scaled by host core count
  5. No column exists for throttling

basics

~20 s

docker stats streams a live per-container line: CPU %, MEM USAGE / LIMIT, MEM %, NET I/O, BLOCK I/O and PIDS. The daemon computes them from the containers' cgroup counters on the host, not from tools running inside the containers.

solid answer

~40 s

`docker stats` is the outside-the-container view of resource use. By default it streams and refreshes; `--no-stream` prints one sample and exits, and `--format` selects fields for scripting. The columns are CPU % (CPU time consumed between two samples, scaled by host CPU count, so it can exceed 100% on a multi-core host), MEM USAGE / LIMIT and MEM %, NET I/O and BLOCK I/O as cumulative totals since the container started, and PIDS as the current process and thread count. The daemon reads the containers' cgroup counters on the host, which is why these numbers respect `--memory` and `--cpus` while `free` and `top` inside the container do not. Two caveats: with no `--memory` set, the LIMIT column shows the host's total memory, and there is no throttling column.

code

bash · 2 lines
bash
docker stats --no-stream \
  --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}} {{.PIDs}}' reranker

go deeper

for a junior

Be able to run it and read every column aloud, including that CPU % can exceed 100% on a multi-core host and that the I/O columns are cumulative totals. Know --no-stream for scripts.

for a middle

Explain that the daemon sources the figures from cgroup counters on the host, which is why they respect limits and work on an image with no shell, and why the LIMIT column falls back to host memory when none was set.

for a senior

Show that you know its blind spots and reach past it deliberately: no throttling counter, no per-process breakdown, no history. Say which further evidence you would gather and from where.

for a principal

Frame it as an interactive spot check, not a monitoring strategy. Decide what is collected continuously for every container across the fleet so that incident review never depends on someone having had a terminal open at the right moment.

### What the command is `docker stats` asks the daemon for a live resource sample of running containers and prints one line each, refreshing until interrupted. With no arguments it covers every running container; give it names or IDs to narrow it. Two flags make it usable in scripts: - `--no-stream` takes a single sample and exits, so the output can be piped. - `--format` renders chosen fields with a Go template, for example `'{{.Name}} {{.CPUPerc}} {{.MemUsage}}'`. ### The columns, one by one **CPU %** - CPU time the container's cgroup consumed between two samples, expressed against elapsed wall time and scaled by the number of host CPUs. On a multi-core host this legitimately exceeds 100%: a container using four cores fully shows about 400%. Because it is a delta between samples, the very first line after startup can look odd, and a single `--no-stream` reading is a short snapshot rather than a trend. **MEM USAGE / LIMIT** - current memory charged to the container's cgroup, and the limit set for it. The important subtlety: if the container was started without `--memory`, the LIMIT half falls back to the host's total memory, which reads like a limit but is not one. Usage is derived from the cgroup counter with inactive file cache subtracted, so it approximates a working set rather than counting every reclaimable page the container has touched. **MEM %** - usage as a share of that LIMIT column, and therefore meaningless when the container is unlimited, because it is then a share of the whole machine. **NET I/O** and **BLOCK I/O** - cumulative bytes received/sent and read/written since the container started, not rates. To get a rate, take two samples and diff. Block I/O counts I/O accounted to the cgroup at the block-device layer, so reads served from the host page cache do not appear, and traffic through some volume backends may not be attributed to the container at all. **PIDS** - the number of processes and threads currently in the container's cgroup. This is the column that catches a thread-leak or a fork storm long before memory does. ### Where the numbers actually come from Every figure is read by the daemon from the host: the container's cgroup counters plus the network statistics of its interfaces. Nothing is executed inside the container, which has three consequences worth stating in an interview. First, `docker stats` works on an image with no shell and no tools at all - a scratch or distroless image reports normally. Second, the numbers respect the limits, unlike `free`, `top` or `nproc` run inside the container, which read host-wide procfs files and therefore report the machine. When someone says "the container thinks it has 128 GiB", `docker stats` is the correction. Third, the reading is the daemon's, so it stops the moment the container stops. `docker stats` shows running containers; a container that has already exited leaves no sample behind. Post-mortem questions need other evidence. ### What it deliberately does not show - **CPU throttling.** There is no column for it. A container being stopped at the end of every scheduling period shows a modest CPU % and looks healthy here. The throttling counters live in the container's cgroup `cpu.stat`, and missing this is the single most common misreading of a `docker stats` screen. - **Per-process detail.** One line per container, never per process. To see which process inside is burning the CPU you need a different tool. - **Disk space used by images, layers or volumes.** BLOCK I/O is throughput, not occupancy; storage consumption is a different question entirely. - **History.** It samples now. Anything about last night comes from a collector that has been recording these counters continuously. ### Cost and habits Each refresh is a request to the daemon per container, so leaving `docker stats` streaming across a host with a hundred containers adds real work for the daemon; in scripts and health checks, always use `--no-stream` with `--format` and take the samples you need. In practice the habit that matters is pairing it with the cgroup files: `docker stats` answers "how much is it using against its limit", and `cpu.stat` answers "is it being stopped at that limit". Neither question is answerable from inside the container with the classic Unix tools.

  • Why can the CPU % column read 340% when the host has eight cores?
    The percentage is CPU time consumed over elapsed wall time, scaled by the number of host CPUs rather than normalised to a single one. A container using three and a half cores concurrently therefore reads about 350%. The ceiling is roughly the core count times one hundred, so a value above 100% is normal for a multi-threaded container and is not an error.
  • A container shows MEM USAGE / LIMIT of 900MiB / 125.6GiB. What does that tell you?
    That the container was started without a memory limit, so the LIMIT column has fallen back to the host's total memory. MEM % is then a share of the whole machine and is not useful. It also means nothing will stop this container from growing until the host itself is under pressure, which is worth flagging regardless of the current usage.
  • Why does `docker stats` still work against an image containing no shell or utilities?
    Because nothing runs inside the container to produce the numbers. The daemon reads the container's cgroup counters and network interface statistics on the host and formats them. That is also why the figures respect the container's limits, while the same measurements taken with tools inside the container would report the host's totals.

saying these in an interview costs you the question

  • Thinks docker stats runs top inside each container
  • Says CPU % above 100 must be a bug
  • Reads the LIMIT column as a real limit when none was set
  • Treats NET I/O and BLOCK I/O as rates rather than totals
  • Expects docker stats to reveal CPU throttling
  • Expects to see stats for a container that has already exited

context