Monitor Container CPU and Memory with `podman stats`

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
Applies to Linux hosts with Podman where you need live or point-in-time CPU, memory, PID, and I/O utilization for containers or pods
Privilege Rootful examples on the lab host; rootless cgroup and network-stat caveats noted where behavior differs
Scope podman stats and podman pod stats, default streaming and --no-stream, --all, --format Go templates and JSON, column meanings, CPU and memory interpretation, PIDs, podman top with host PID descriptors, cgroup version checks, reading cgroup v2 memory.current, cpu.stat, and pids.current, rootless cgroup v1 failure, and rootless network I/O limitations. Does not cover setting CPU or memory limits, Prometheus or Grafana, or generic Linux performance tuning.
Related guides Inspect Podman containers
List containers with podman ps
View and follow container logs
Podman pods
Podman architecture

podman ps tells you a container is running. podman stats tells you how hard it is working — CPU percent, memory footprint, process count, and I/O counters pulled from cgroups. This guide focuses on reading those numbers on Podman 5.8.2 and cross-checking them against cgroup v2 files on the host.

Setting limits belongs in the resource-limits guide; here you only measure what the workload already consumes.


Lab setup

Create a working directory and a container that keeps one CPU busy without exhausting the host:

bash
mkdir -p ~/podman-stats-lab && cd ~/podman-stats-lab

Start a detached container that repeatedly reads from /dev/zero so CPU stays busy:

bash
podman run -d --name stats-demo docker.io/library/alpine:latest sh -c 'i=0; while true; do i=$((i+1)); dd if=/dev/zero of=/dev/null bs=1M count=10 2>/dev/null; done'

Add a mostly idle container for contrast:

bash
podman run -d --name stats-idle docker.io/library/alpine:latest sleep 3600

Give stats-demo a few seconds to warm up before you read stats.


Start with podman stats on one container

Stream live utilization for a single target (press Ctrl+C to stop):

bash
podman stats stats-demo

For documentation, capture one snapshot with --no-stream (covered in depth later):

bash
podman stats --no-stream stats-demo

Sample output on this host:

output
ID            NAME        CPU %       MEM USAGE / LIMIT  MEM %       NET IO          BLOCK IO    PIDS        CPU TIME    AVG CPU %
064bd4dcfd35  stats-demo  93.36%      827.4kB / 8.052GB  0.01%       1.955kB / 628B  0B / 0B     2           11.29544s   93.36%

Column meanings on Podman 5.8.2:

  • ID — short container ID
  • NAME — container name
  • CPU % — CPU utilization for the sampling window
  • MEM USAGE / LIMIT — cgroup memory use versus limit
  • MEM % — memory use as a percent of the limit column
  • NET IO — received / transmitted network bytes
  • BLOCK IO — read / write block bytes
  • PIDS — process count in the cgroup
  • CPU TIME — cumulative CPU time
  • AVG CPU % — average CPU utilization over the container's observed lifetime rather than only the latest sampling interval; it may initially match CPU % but can diverge as workload changes

An idle-only example would show ~0% CPU and one PID — stats-idle fills that role in the comparisons below.


Monitor all running containers

Without arguments, podman stats watches every running container:

bash
podman stats --no-stream

Sample output:

output
ID            NAME                CPU %       MEM USAGE / LIMIT  MEM %       NET IO          BLOCK IO    PIDS        CPU TIME    AVG CPU %
05212f2a4518  stats-idle          0.06%       380.9kB / 8.052GB  0.00%       7.612kB / 698B  0B / 0B     1           12.707ms    0.06%
064bd4dcfd35  stats-demo          92.83%      823.3kB / 8.052GB  0.01%       1.955kB / 628B  0B / 0B     2           11.430495s  92.83%

Stopped containers are omitted by default. Include them with --all:

bash
podman stats --no-stream --all

When every listed container is already running, --all matches the default output. The flag matters when you want stats rows for created or exited containers too.


Get a one-time snapshot with --no-stream

Scripts should almost always disable the live refresh loop:

bash
podman stats --no-stream

Target specific containers by name:

bash
podman stats --no-stream stats-demo stats-idle

Without --no-stream, Podman redraws the table continuously — fine for troubleshooting, awkward for cron jobs or CI parsers.


Format podman stats output

Pull selected fields with a Go template:

bash
podman stats --no-stream --format '{{.Name}} {{.CPU}} {{.MemUsage}}' stats-demo stats-idle

Sample output:

output
stats-demo 89.03190481909083 577.5kB / 8.052GB
stats-idle 0.05968412826535389 380.9kB / 8.052GB

.CPU is a floating-point value, not a pre-formatted percent string. Other useful placeholders include .MemPerc, .NetIO, .BlockIO, and .PIDs.

Emit JSON for tools that already speak JSON:

bash
podman stats --no-stream --format json stats-demo

Sample output (trimmed):

output
[
 {
  "id": "064bd4dcfd35",
  "name": "stats-demo",
  "cpu_percent": "88.37%",
  "mem_usage": "995.3kB / 8.052GB",
  "mem_percent": "0.01%",
  "net_io": "1.955kB / 628B",
  "block_io": "0B / 0B",
  "pids": "2"
 }
]

JSON field names use snake_case (cpu_percent, mem_usage, net_io). You do not need every template key memorized — start from --format json once, then trim.


Understand CPU percentage

Check how many logical CPUs the host exposes:

bash
nproc

Sample output:

output
3

Compare idle versus busy containers:

bash
podman stats --no-stream --format '{{.Name}} {{.CPU}}' stats-idle stats-demo

Sample output:

output
stats-idle 0.033
stats-demo 86.84

On this three-core host, a single hot loop in stats-demo reported roughly 87–94% CPU, which is close to saturating one logical CPU. Podman CPU percentages are per-core style: a container using two cores concurrently can approach 200%, and a workload capable of using all three cores on this host could approach 300%. stats-idle stayed near zero.

To see which process burns CPU inside the container, use podman top in the next section — stats alone does not name the command.


Understand memory usage

Memory columns tie directly to cgroup accounting:

bash
podman stats --no-stream stats-demo

Sample output:

output
064bd4dcfd35  stats-demo  92.83%  823.3kB / 8.052GB  0.01%  ...
  • MEM USAGE — current cgroup memory consumption
  • MEM LIMIT — effective ceiling (8.052GB here with no --memory flag set)
  • MEM % — usage divided by the limit column

With no explicit --memory on the container, the limit reflects the cgroup hierarchy's effective cap — on this host it matched available host memory while memory.max in cgroup v2 read max (no hard cgroup limit). Do not assume the limit column always equals installed RAM without checking your cgroup tree.


Monitor process count (PIDS)

Spawn background shells inside the busy container:

bash
podman exec stats-demo sh -c 'for i in 1 2 3 4 5; do sleep 3600 & done'

Read the updated PID count:

bash
podman stats --no-stream --format '{{.Name}} {{.PIDs}}' stats-demo

Sample output:

output
stats-demo 7

The PIDS column rose from 2 to 7 after five background sleep processes joined the cgroup. Cross-check with podman top when you need process names, not just a count.


Use podman top

List processes as the container sees them:

bash
podman top stats-demo

Sample output:

output
USER        PID         PPID        %CPU        ELAPSED        TTY         TIME        COMMAND
root        1           0           4.602       21.729155096s  ?           1s          sh -c i=0; while true; do i=$((i+1)); dd if=/dev/zero of=/dev/null bs=1M count=10 2>/dev/null; done

Podman adds descriptors that map container IDs to host IDs — especially useful rootless:

bash
podman top stats-demo pid hpid user huser pcpu args

Sample output (trimmed):

output
PID         HPID        USER        HUSER       %CPU        COMMAND
1           323627      root        root        4.384       sh -c i=0; while true; do ...

Descriptor meanings:

  • pid — PID inside the container namespace
  • hpid — corresponding PID on the host
  • user — user inside the container
  • huser — user as resolved on the host

A quick capability glance without turning this into a security audit:

bash
podman top stats-demo pid seccomp capeff args

Sample output (trimmed):

output
PID         SECCOMP     EFFECTIVE CAPS                                                                                   COMMAND
1           filter      CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT  sh -c i=0; while true; do ...

podman top is a snapshot; podman stats streams cgroup totals. Use both when monitoring intent needs process names and resource percentages together.


Monitor pod resources with podman pod stats

Pods expose per-member statistics — not a single aggregate row:

bash
podman pod create --name stats-pod

Add a tight loop as the pod workload:

bash
podman run -d --pod stats-pod --name stats-pod-worker docker.io/library/alpine:latest sh -c 'while true; do :; done'

After the worker warms up, read pod stats once:

bash
podman pod stats --no-stream stats-pod

Sample output:

output
POD           CID           NAME                CPU %       MEM USAGE/ LIMIT  MEM %       NET IO          BLOCK IO    PIDS
7eae5b1cc3d8  a46ad33e8354  7eae5b1cc3d8-infra  0.04%       213kB / 8.052GB   0.00%       6.876kB / 698B  -- / --     1
7eae5b1cc3d8  9b021cbefaee  stats-pod-worker    90.98%      426kB / 8.052GB   0.01%       6.876kB / 698B  -- / --     1

Each line is one pod member — infra container plus workload containers. BLOCK IO may show -- / -- for pod stats even when container stats show 0B / 0B.


Check cgroup version first

Before reading cgroup files, confirm what Podman uses on the host:

bash
podman info --format '{{.Host.CgroupsVersion}}'

Sample output on RHEL 10.2:

output
v2

Cgroup manager:

bash
podman info --format '{{.Host.CgroupManager}}'

Sample output:

output
systemd

Modern RHEL and Fedora hosts report v2 with systemd. Older distributions on cgroup v1 behave differently — especially rootless stats, covered next.


Rootless cgroup v1 limitation

podman stats does not work for rootless Podman on cgroup v1. Podman cannot read the cgroup statistics rootless cgroup v1 exposes reliably, so the command fails or returns empty data on those hosts.

RHEL 10 uses cgroup v2 by default, so this mostly affects older installations. This guide does not walk through enabling cgroup v1 — migrate the host or run rootful stats where policy allows.


Rootless network statistics caveat

Even on cgroup v2, Podman documents incomplete network I/O accounting for rootless containers — NET IO may read 0B / 0B or stay misleadingly low despite real traffic. Do not treat a zero NET IO column as proof the network is down.

On a rootless cgroup v2 test here (after enabling user linger), a tiny container still reported 284B / 902B NET IO. Other rootless networking stacks may not populate the column. Verify connectivity with application-level checks when NET IO looks wrong.


Find the container cgroup path

Start from the container init PID on the host:

bash
podman inspect --format '{{.State.Pid}}' stats-demo

Sample output:

output
323627

Follow the cgroup membership file for that PID:

bash
cat /proc/323627/cgroup

Sample output:

output
0::/machine.slice/libpod-064bd4dcfd356746088660040513f6e71f37772a882db3b2930c94137f39b319.scope/container

The path after 0:: maps under /sys/fs/cgroup/ on cgroup v2. Paths differ for rootless users, Quadlet units, and pods — always discover the path from the running container instead of hard-coding a directory.

systemd-cgls can help locate the same scope in the systemd hierarchy when you think in units rather than /proc paths.


Read memory from cgroup v2

With the path above, read current and maximum memory (replace CGROUP_PATH with your discovered path):

bash
cat /sys/fs/cgroup/machine.slice/libpod-064bd4dcfd356746088660040513f6e71f37772a882db3b2930c94137f39b319.scope/container/memory.current

Sample output:

output
2387968

The hard limit for the same cgroup — max means no cgroup-specific cap was set:

bash
cat /sys/fs/cgroup/machine.slice/libpod-064bd4dcfd356746088660040513f6e71f37772a882db3b2930c94137f39b319.scope/container/memory.max

Sample output:

output
max
  • memory.current — bytes in use by the cgroup now
  • memory.max — hard limit; max means no cgroup-specific cap

2387968 bytes is about 2.3 MiB — the same order of magnitude as the ~800kB–3.5MB MEM USAGE Podman reported across snapshots. Small gaps are normal because stats sampling and memory.current reads happen at slightly different times.


Read CPU statistics from cgroup v2

CPU accounting lives in cpu.stat:

bash
cat /sys/fs/cgroup/machine.slice/libpod-064bd4dcfd356746088660040513f6e71f37772a882db3b2930c94137f39b319.scope/container/cpu.stat

Sample output (trimmed):

output
usage_usec 25175822
user_usec 12446422
system_usec 12729399
nr_throttled 0
throttled_usec 0

usage_usec is total CPU time in microseconds. throttled_usec stays zero until a CPU quota throttles the cgroup — when you set CPU limits, compare cpu.max and throttling fields against Podman resource limits.


Read PID limits and usage

Process count from cgroup files:

bash
cat /sys/fs/cgroup/machine.slice/libpod-064bd4dcfd356746088660040513f6e71f37772a882db3b2930c94137f39b319.scope/container/pids.current

Sample output:

output
7

Maximum allowed:

bash
cat /sys/fs/cgroup/machine.slice/libpod-064bd4dcfd356746088660040513f6e71f37772a882db3b2930c94137f39b319.scope/container/pids.max

Sample output:

output
2048

pids.current matched the PIDS column (7) after the background sleep processes were started. Setting a lower cap belongs in the resource-limits guide.


podman stats vs podman top

Topic podman stats podman top
Focus cgroup resource totals per-process list
Data CPU %, memory, I/O, PID count PID, user, command, capabilities
Mode streaming by default; --no-stream for snapshots one-shot snapshot
Best for capacity and saturation checks finding which process is hot

Both answer monitoring questions from different angles — totals versus process detail.


Troubleshooting

Symptom Likely cause Fix
podman stats empty or errors rootless cgroup v1 host Upgrade to cgroup v2 or use rootful Podman
NET IO 0B / 0B rootless documented network accounting gap verify with app traffic; do not assume network failure
CPU % near 100% on one busy loop one core saturated on multi-core host expected; compare with nproc and podman top
MEM LIMIT shows host RAM no --memory set read memory.max in cgroup; set limits if you need a cap
PIDS mismatch short-lived children re-run --no-stream; use podman top for live list
cgroup path not under machine.slice rootless, Quadlet, or pod layout discover via /proc/PID/cgroup each time

References


Summary

podman stats is the first tool for container capacity checks — CPU percent, memory use against the effective limit, process count, and I/O counters updated from cgroups. Use --no-stream in scripts and --format when you need JSON or selected columns without parsing a live table.

On this three-core host, one busy loop reported roughly 90% CPU while an idle neighbor stayed near zero. Memory numbers align with cgroup v2 memory.current when you discover the path through podman inspect and /proc/PID/cgroup. podman pod stats lists each pod member separately, including the infra container — not one combined row.

Rootless cgroup v1 cannot support stats at all. Rootless cgroup v2 may show incomplete NET IO even when traffic flows. Pair stats with podman top when you need host PIDs and command lines, and with cgroup files when you need kernel-level confirmation. To cap what a container may consume, move on to resource limits — this page only measures current use.


Frequently Asked Questions

1. What is the difference between podman stats and podman top?

podman stats reports cgroup-level resource totals — CPU percent, memory usage and limit, network and block I/O, and process count — and can stream continuously. podman top lists individual processes inside the container with PID, user, and command details. Use stats for capacity monitoring; use top to see which process is consuming CPU.

2. How do I get a one-time podman stats snapshot for scripts?

Add --no-stream. podman stats --no-stream prints one row per container and exits instead of refreshing the terminal. Combine with --format for machine-readable output, for example podman stats --no-stream --format '{{.Name}} {{.CPU}} {{.MemUsage}}' CONTAINER.

3. Why does podman stats show 0B for NET IO in rootless mode?

Rootless Podman on cgroup v1 cannot run stats at all. On cgroup v2, network I/O counters in podman stats are often incomplete or zero on rootless hosts because Podman cannot read the same network accounting paths as rootful cgroup v2. Verify connectivity with application tests or external tools — do not assume the network is broken when NET IO is blank.

4. What does the MEM LIMIT column mean when I did not set --memory?

Without an explicit container memory limit, Podman shows the effective cgroup ceiling — on the lab host that appeared as about 8 GiB, matching host RAM while memory.max in cgroup v2 read max. The limit column is not always physical RAM; it reflects what the cgroup allows.

5. How do I verify podman stats memory against the kernel?

Read the container init PID with podman inspect --format '{{.State.Pid}}' CONTAINER, follow /proc/PID/cgroup to the cgroup v2 path, then cat memory.current and memory.max under that directory. Values should be the same order of magnitude as the MEM USAGE column, allowing for timing and accounting differences.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)