How do you read the columns of `docker stats`, and when do you need --no-stream?
answer
- The table never stops on its own
- One column is a host-wide percentage
- The ceiling may just be the machine
- Two of the columns are running totals
- Thread count is its own column
basics
~20 sdocker stats streams a live per-container table of CPU %, MEM USAGE / LIMIT, MEM %, NET I/O, BLOCK I/O and PIDS, refreshing until interrupted. --no-stream prints one sample and exits, which is what any script or CI step needs so the command terminates.
solid answer
~50 s`docker stats` polls the daemon for each running container and prints a refreshing table. **CPU %** is normalised against the whole host, so it can exceed 100% on a multi-core box — 340% means roughly three and a half cores' worth. **MEM USAGE / LIMIT** shows current usage against the container's memory limit, and when no limit was set the limit column is simply the host's total RAM, which fools people into thinking a cap exists. **MEM %** is usage over that limit. **NET I/O** and **BLOCK I/O** are cumulative totals since the container started, not rates, so you must diff two samples to get throughput. **PIDS** counts processes and threads in the container. By default the command streams forever; `--no-stream` takes a single snapshot and exits, `--all` includes stopped containers, and `--format` with template fields such as `{{.Name}}` and `{{.MemUsage}}` makes the output machine-readable. It is a spot check, not a monitoring system.
code
bash · 14 lines# Live, refreshing table for every running container (Ctrl-C to stop)
docker stats
# One sample and exit - the only safe form inside a script or CI step
docker stats --no-stream
# Narrow to specific containers and make it machine-readable
docker stats --no-stream --format '{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}' worker cache
# Keep a header row with the table prefix
docker stats --no-stream --format 'table {{.Name}}\t{{.MemPerc}}\t{{.PIDs}}'
# Two samples a few seconds apart turn cumulative NET I/O into a rate
docker stats --no-stream --format '{{.Name}} {{.NetIO}}' workergo deeper
Know the command exists, what each column is called, and that it keeps running until you interrupt it — reach for --no-stream the moment the command is inside a script.
Explain the mechanics: CPU % is host-normalised, MEM LIMIT defaults to host RAM when uncapped, NET and BLOCK I/O are cumulative, and each refresh is a per-container call to the daemon.
Demonstrate judgement about what it can and cannot answer — no history, no percentiles, a skewed first sample — and show a repeatable snapshot pattern you actually use during an incident.
Own the boundary: docker stats is a spot check, and building operations around ad-hoc terminal readings instead of a real measurement path is the failure mode to name and prevent.
## What the command does `docker stats` asks the daemon for a resource-usage sample for each running container and renders them as a table that refreshes roughly once a second. Each sample is a separate call to the engine per container; the CLI does the arithmetic that turns raw counters into the percentages you see. This matters for two reasons: the numbers are the engine's view, not the application's, and running the command against a host with hundreds of containers is not free. ## Column by column **CONTAINER ID / NAME** — identity. Use `--format` if you want one and not the other. **CPU %** — the container's CPU consumption expressed against the *whole host's* capacity, so on a 16-core machine a single fully busy thread shows as roughly 6.2% and a container saturating four cores shows around 400%. Candidates who insist the column cannot exceed 100% have never watched a busy container. The figure is derived from the delta between two samples, so the very first line after the command starts is the least trustworthy one. **MEM USAGE / LIMIT** — current usage on the left, the ceiling on the right. The most misread column on the screen: when the container was started with no memory limit, the right-hand figure is the host's total memory. Seeing `412.7MiB / 31.29GiB` tells you nothing about a configured cap; it tells you there isn't one. **MEM %** is simply the left divided by the right, so it is equally meaningless when no limit was set. A second subtlety: the Docker CLI reports memory usage with page-cache usage subtracted, while the raw API response carries both the total and the cache figure so a client can decide for itself. That is why `docker stats` and a naive reading of the raw API can disagree, and why the number rarely matches what a process inside the container reports for itself — the application sees its own heap, the engine sees the whole container's accounting. **NET I/O** and **BLOCK I/O** — cumulative received/sent bytes and cumulative read/written bytes **since the container started**. They are totals, not rates. A container that transferred a lot at boot and nothing since still shows a big number forever. To get a rate, take two samples and subtract. Block I/O counts I/O that reached a block device, so reads served out of the host page cache do not show up, and writes to tmpfs never do. **PIDS** — the number of processes and threads currently in the container. A steadily climbing PIDS column on a container that should be single-purpose is a thread- or process-leak smell, and it is one of the few genuinely diagnostic columns for that. ## Streaming versus a snapshot By default the command never returns; it repaints the table until you interrupt it. Any non-interactive use therefore needs `--no-stream`, which collects one sample and exits — otherwise a CI step or a shell script hangs forever, and without a TTY the output degrades into repeated blocks of text rather than a redrawn table. Related flags: - `--all` / `-a` includes non-running containers, which appear with zeroed columns; useful to confirm a name exists at all. - `--format` takes a Go template over fields `.Container`, `.ID`, `.Name`, `.CPUPerc`, `.MemUsage`, `.MemPerc`, `.NetIO`, `.BlockIO`, `.PIDs`; prefixing the template with `table` restores a header row. - Passing explicit container names or IDs limits the sample to those, which is much cheaper than sampling everything on a busy host. ## How to use it well Treat `docker stats` as a stethoscope, not a chart. The questions it answers well are immediate and comparative: which of these containers is the hot one right now, is this container anywhere near its ceiling, is its thread count climbing, has it moved any network traffic at all since it started. The questions it answers badly are historical (there is no retention — close the terminal and the data is gone), aggregate (there is no percentile, no rate, no alert) and precise (the sampling interval is coarse and the first sample is skewed). A practical pattern is a scripted snapshot: `docker stats --no-stream --format '{{.Name}}\t{{.MemPerc}}\t{{.PIDs}}'` run before and after a load test, or captured when a health check first fails, gives you a comparable pair of readings without standing up anything. Another is running it narrowed to one container while you reproduce a problem, so the refresh actually tracks the thing you are exercising. The honest framing for an interview is: `docker stats` tells you what the engine currently accounts to each container, in a form good enough to decide where to look next.
- `docker stats` shows CPU % at 380% for one container. Is that a bug?No. The column is normalised against the whole host rather than a single core, so a container keeping roughly four cores busy reads around 400%. On a 16-core host that container is using about a quarter of the machine. The only surprising reading would be a percentage far above the host's core count times one hundred.
- Why does the LIMIT side of MEM USAGE / LIMIT show the host's total RAM for some containers?Because those containers were started without a memory limit, so there is no container-specific ceiling for the column to display and the engine falls back to the host's total memory. The consequence is that MEM % is meaningless for them — it is a fraction of the machine, not of an allowance — and a container can grow until the host itself is under pressure.
- Why is NET I/O in `docker stats` a poor way to see current throughput?It is a cumulative total of bytes received and sent since the container started, not a rate. A container that pulled a large dataset at startup shows a large figure indefinitely, and an idle container's figure never moves. To get throughput you take two `--no-stream` samples a known interval apart and subtract, or read the same counters from a real metrics pipeline.
- Why does the memory figure in `docker stats` disagree with what the application inside the container reports?They measure different things. The engine accounts everything charged to the container, and the CLI subtracts page-cache usage before displaying it; the application reports only its own runtime's view, such as a heap. Filesystem cache, other processes in the container and runtime overhead all sit in the gap, so the two figures should be expected to differ rather than reconciled.
saying these in an interview costs you the question
- Claims CPU % cannot exceed 100 percent
- Reads the LIMIT column as a configured cap
- Treats NET I/O as a current rate
- Runs it unflagged inside a script and hangs the job
- Calls it a monitoring solution with history
- Expects the number to match the app's own heap figure