Podman can enforce resource limits on containers without requiring root privileges by leveraging cgroups v2, but the setup involves a few critical pieces that often trip people up.

Here’s how it looks in the wild. Let’s say you want to run a simple Python web server in a container and limit its CPU usage to 50% of one core and its memory to 256MB.

First, ensure your system is configured for cgroups v2. Most modern Linux distributions are, but you can check with mount | grep cgroup2. You should see a line like cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot). If you see cgroup (singular) and xgroup in the output, you’re likely still on cgroups v1.

Now, let’s create a Podman pod and add a container to it with resource limits.

# Create a Podman pod named 'my-limited-pod'
podman pod create --name my-limited-pod --infra=false

# Create a container named 'webserver' within the pod,
# limiting CPU to 50000 (50% of one CPU) and memory to 256MB.
# The --cpus flag expects a fractional value representing CPU shares,
# but for cgroups v2's cpu.max, it's expressed as a ratio.
# Podman translates --cpus 0.5 to a cpu.max of 50000 100000.
podman run --pod my-limited-pod --name webserver \
  --cpus 0.5 \
  --memory 256m \
  docker.io/library/python:3.9-slim \
  python -m http.server 8000

To verify, you can inspect the cgroup created for this pod. Podman places pods in their own cgroups. The path will look something like /sys/fs/cgroup/user.slice/user-<UID>.slice/user@<UID>.service/session-*.scope/cgroup.controllers/machine.slice/machine-qemu-<container_id>.scope. However, Podman often uses a more abstract path for cgroup v2. A more reliable way is to find the cgroup associated with the pod’s PID.

First, get the PID of a process inside the pod:


PODMAN_POD_PID=$(podman pod inspect --format '{{.InfraContainerID}}' my-limited-pod | xargs podman container inspect --format '{{.State.Pid}}')

CGROUP_PATH=$(grep -E '/sys/fs/cgroup/.*user\.slice/user-.*\.slice/user@.*\.service/session-.*\.scope' /proc/$PODMAN_POD_PID/cgroup | head -n 1 | cut -d: -f1)

Then, check the cpu.max and memory.max files within that cgroup’s cpu and memory controller directories.

cat $CGROUP_PATH/cpu.max
# Expected output: 50000 100000

cat $CGROUP_PATH/memory.max
# Expected output: 268435456 (which is 256 * 1024 * 1024)

The cpu.max value 50000 100000 means the container is guaranteed 50,000 out of a total of 100,000 CPU "credits" per second. This effectively limits it to 50% of one CPU core. The memory.max value is the limit in bytes.

The core problem this solves is running containers with resource isolation without needing elevated privileges. Historically, cgroups and resource management were tightly coupled with root permissions. Podman, by leveraging user-level cgroup slices (like user.slice), allows a non-root user to create and manage cgroups for their own processes, effectively creating sandboxed environments with enforced limits.

Internally, Podman interacts with the system’s cgroup v2 hierarchy. When you specify --cpus or --memory, Podman translates these user-friendly flags into the appropriate cgroup v2 controller settings (cpu.max, memory.max, etc.) within the cgroup created for the pod. The system’s systemd-logind or similar mechanisms are responsible for creating the initial user slices, and Podman then delegates sub-cgroups for pods and containers within that user’s slice.

The --cpus flag in Podman (for cgroups v2) is a bit of a simplification. It maps to cpu.max and cpu.weight. For --cpus N, it sets cpu.max to N * 100000 and 100000. This means it’s allocating a proportional share, and 100000 represents the "total available" CPU units for that slice. If you have multiple CPU cores, --cpus 0.5 doesn’t strictly mean 50% of one core in the sense of a hard cap, but rather 50% of the total CPU time available to that cgroup, which is often normalized to one core’s worth of capacity for simplicity in the cpu.max representation. For hard limits or more complex scheduling, you’d need to dive into cpu.pressure and other cpu controller parameters, which Podman doesn’t directly expose via simple flags.

The next hurdle you’ll likely encounter is understanding how Podman handles networking and storage for these non-root pods, especially when you start needing more advanced configurations or want to use the rootlessnet features.

Want structured learning?

Take the full Podman course →