docker stats

docker stats shows live resource usage for running containers. It matters because many container problems are not crashes: they are slow memory leaks, CPU saturation, unexpected network traffic, disk-heavy writes, or too many processes inside one container.

The command is a quick inspection tool, not a full monitoring platform. Use it when you need an immediate answer at the terminal: which container is busy, how close it is to its memory limit, and whether a service is doing the kind of work you expect.

Overview: How docker stats works

docker stats asks the Docker daemon for resource accounting data about containers. The Docker CLI is only the client; the daemon owns the containers, talks to the container runtime, and reads operating-system counters. On Linux, those counters come mostly from control groups, usually called cgroups. Docker uses cgroups to limit and measure CPU, memory, I/O, and process counts for each container. With Docker Desktop on macOS or Windows, the containers run inside a lightweight Linux VM, so the numbers describe resource use inside that VM, not direct native host processes.

A container is an isolated process tree created from an image. The image is read-only layers, while the running container has a thin writable layer and resource controls around its processes. docker stats does not inspect the image layers, container logs, or application health directly. Instead, it samples runtime counters for the container’s current process tree and streams a table that refreshes until you stop it.

The most useful columns are CPU %, MEM USAGE / LIMIT, MEM %, NET I/O, BLOCK I/O, and PIDS. CPU can be surprising: on a machine with multiple cores, a container using one full core may show about 100%, while a container using two full cores may show about 200%. Memory usage is shown against the container’s configured memory limit. If no explicit limit is set, Docker often shows the host or Docker Desktop VM memory limit, which can make a container look less constrained than it would be in production.

NET I/O is cumulative bytes received and sent through the container’s network interfaces since it started. BLOCK I/O is cumulative disk-like read and write activity through the container’s block devices and writable layer. A write-heavy container might be creating logs, temporary files, database pages, or cache files. PIDS counts processes and threads in the container’s process namespace. A high or steadily rising PID count often points to a worker leak or a process supervisor spawning children that never exit.

Syntax

docker stats [OPTIONS] [CONTAINER...]

Without container names or IDs, Docker shows all running containers. With one or more container names or IDs, it filters the stream to only those containers.

Option Purpose
--all, -a Show all containers, including stopped containers. Stopped containers usually display zeroes.
--no-stream Print one snapshot and exit. This is useful in scripts and before-and-after checks.
--no-trunc Do not truncate container IDs in the output.
--format Format the output using Docker’s Go template syntax, such as table {{.Name}} {{.CPUPerc}}.

The most common forms are:

docker stats
docker stats --no-stream web redis
docker stats --no-stream --format 'table {{.Name}}	{{.CPUPerc}}	{{.MemUsage}}	{{.PIDs}}'

Examples

Watch all running containers

docker run -d --name stats-nginx -p 8080:80 nginx:1.27-alpine
docker stats stats-nginx

Output:

CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT     MEM %     NET I/O        BLOCK I/O     PIDS
9b12c8a3152d   stats-nginx   0.03%     8.31MiB / 7.65GiB     0.11%     1.2kB / 0B     0B / 0B       5

This starts a small Nginx container and then streams live stats for it. The published port is only there so you can generate traffic if you want to test NET I/O; publishing a port is not required for docker stats. Press Ctrl+C to stop watching. The container keeps running because you stopped only the stats command, not the container.

Take a script-friendly snapshot

docker stats --no-stream stats-nginx

Output:

CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT     MEM %     NET I/O        BLOCK I/O     PIDS
9b12c8a3152d   stats-nginx   0.01%     8.45MiB / 7.65GiB     0.11%     2.4kB / 0B     0B / 0B       5

--no-stream returns a single measurement and exits. That makes it better for documentation, incident notes, shell scripts, or comparing two points in time. Remember that CPU is a sample, so one snapshot can miss short spikes. For spike hunting, leave the stream open or use a monitoring system that records samples over time.

Format only the fields you need

docker stats --no-stream --format 'table {{.Name}}	{{.CPUPerc}}	{{.MemPerc}}	{{.NetIO}}	{{.PIDs}}' stats-nginx

Output:

NAME          CPU %     MEM %     NET I/O        PIDS
stats-nginx   0.02%     0.11%     2.4kB / 0B     5

The formatted view removes columns you do not care about. This is helpful when a terminal is narrow or when you want to paste focused evidence into an issue. Docker’s template fields include values such as .Container, .Name, .ID, .CPUPerc, .MemUsage, .MemPerc, .NetIO, .BlockIO, and .PIDs.

Compare containers with memory limits

docker run -d --name limited-nginx --memory 128m nginx:1.27-alpine
docker stats --no-stream limited-nginx

Output:

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O     PIDS
2a41a75b93ef   limited-nginx   0.04%     8.2MiB / 128MiB       6.41%     656B / 0B   0B / 0B       5

The same small service now shows memory usage against a 128MiB limit instead of the host or VM memory ceiling. That percentage is often the number you need during debugging: a container using 80MiB may be fine with a 1GiB limit but risky with a 96MiB limit.

How it works step by step

  1. You run docker stats in a terminal. The Docker CLI connects to the Docker daemon through the configured Docker socket or remote API endpoint.
  2. The daemon resolves the requested container names or IDs. If you did not provide any, it selects all running containers, unless --all is also present.
  3. For each container, Docker reads resource counters maintained by the host kernel. On Linux this is mainly cgroup data for CPU time, memory usage, I/O, and PID counts.
  4. Docker calculates rates or percentages from raw counters. CPU percentage is based on how much CPU time the container used between samples compared with available CPU time.
  5. The daemon sends stats data back to the CLI. In streaming mode, the CLI redraws the table repeatedly. With --no-stream, it prints one table and exits.

Because these numbers are sampled and calculated, treat them as operational signals rather than perfect accounting. They are excellent for finding direction: which container deserves the next inspection with docker logs, docker exec, application metrics, or profiling tools.

Common Mistakes

Assuming low CPU means the app is healthy

docker stats --no-stream api

Output:

NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O     PIDS
api       0.00%     312MiB / 512MiB       60.94%    0B / 0B       0B / 0B       37

This container is not using CPU, but that does not prove it is healthy. It may be deadlocked, waiting on a database, unable to accept network traffic, or stuck before its server starts. Fix the investigation by combining docker stats with docker logs api, docker inspect api, and an application-level health check.

Reading memory percentage without checking the limit

docker stats --no-stream worker

Output:

NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O      PIDS
worker    4.11%     900MiB / 15.6GiB      5.63%     18MB / 2MB    4MB / 128MB    22

A low memory percentage can hide a problem if the container has no production-like memory limit. The same 900MiB worker could be near failure in production with a 1GiB limit. Fix this by testing with realistic limits, for example docker run --memory 1g, and by alerting on absolute memory usage as well as percentage.

Expecting docker stats to show stopped containers by default

docker stats old-job

Output:

Error response from daemon: No such container: old-job

If the name is wrong, removed, or not visible to the current Docker context, Docker cannot show it. If the container exists but is stopped, plain docker stats is not the best tool for historical diagnosis. Use docker ps -a to confirm its state, docker logs old-job for prior output, and docker inspect old-job for exit status and configuration. Use docker stats --all --no-stream only when you specifically want stopped containers included in the table.

Best Practices

  • Use docker stats early in an incident to identify the busiest or most constrained container, then switch to logs, traces, profiles, or application metrics for root cause.
  • Use --no-stream when saving evidence in a ticket or comparing before and after a change.
  • Set realistic memory limits in test and staging so MEM % resembles production behavior.
  • Watch absolute memory values, not only percentages, especially on Docker Desktop where the VM limit may differ from the physical machine.
  • Investigate rising PIDS counts. Containers should usually have a predictable process and thread range.
  • Treat high BLOCK I/O as a clue. It may indicate excessive logging, cache churn, temporary files, or a database workload that needs a volume and proper tuning.
  • Clean up containers created during practice with docker rm -f stats-nginx limited-nginx when you are done.

Practice Exercises

  1. Start an nginx:1.27-alpine container with a name of your choice, run docker stats against only that container, then stop the stats stream without stopping the container. Hint: use the container name as the final argument.
  2. Run the same container with a 64m memory limit and take a one-line snapshot. Expected end state: the memory limit column should show roughly 64MiB, not the full host or VM memory limit.
  3. Create a formatted stats command that prints only the name, memory usage, block I/O, and PID count. Hint: use --format with a table template.

Summary

  • docker stats streams live container resource usage from Docker daemon and kernel accounting data.
  • The key columns are CPU percentage, memory usage and limit, network I/O, block I/O, and PID count.
  • --no-stream gives one snapshot; --format makes focused terminal or script output.
  • Percentages only make sense when you understand the limit they are based on.
  • Use docker stats as a fast debugging signal, then confirm the cause with logs, inspect data, and application metrics.