Podman’s resource limits are surprisingly permissive by default, allowing containers to consume as much CPU and memory as the host machine can offer, often leading to unexpected resource contention and instability.
Let’s see this in action. Imagine a simple Python script that spins up a few threads to do some heavy computation and consumes a good chunk of RAM.
import threading
import time
import os
def cpu_hog():
while True:
pass
def memory_hog():
data = []
while True:
data.append(' ' * 1024 * 1024) # Allocate 1MB
if len(data) > 1000: # Limit to 1GB to avoid crashing the host immediately
data.pop(0)
time.sleep(0.001)
if __name__ == "__main__":
print(f"Starting CPU hog thread...")
cpu_thread = threading.Thread(target=cpu_hog)
cpu_thread.daemon = True
cpu_thread.start()
print(f"Starting Memory hog thread...")
memory_thread = threading.Thread(target=memory_hog)
memory_thread.daemon = True
memory_thread.start()
print(f"Container PID: {os.getpid()}")
while True:
time.sleep(10)
If we run this script directly on the host, it will consume CPU and memory as it pleases. Now, let’s run it in a Podman container without any limits:
podman run -d --name my-resource-hog python:3.9 python /app/resource_hog.py
If you podman exec my-resource-hog top (or htop), you’ll see the Python process inside the container hogging CPU and memory. If you had several such containers, they could easily saturate your host.
The problem Podman resource limits solve is precisely this: preventing a single container, or a group of containers, from monopolizing host resources, which can lead to performance degradation for other applications or even system instability.
Podman uses the Linux kernel’s cgroup (control groups) mechanism to enforce these limits. When you set a resource limit, Podman configures the relevant cgroup for the container, telling the kernel how much CPU time or memory that container is allowed to use.
The primary levers you control are CPU and memory.
For CPU, you can set:
--cpus: The number of CPUs the container can use. This is a fractional number, so--cpus 1.5means 1.5 CPU cores.--cpu-shares: A relative weight for CPU time. If one container hascpu-shares=1024and another hascpu-shares=512, the first container will get roughly twice as much CPU time when they are both contending for resources. This is a relative, not absolute, limit.--cpuset-cpus: Pinning the container’s CPU usage to specific CPU cores (e.g.,--cpuset-cpus 0,1uses cores 0 and 1).
For Memory, you can set:
--memoryor-m: The maximum amount of memory the container can use. This can be specified with units likem(megabytes) org(gigabytes), e.g.,--memory 512m.--memory-swap: The total amount of memory and swap space the container can use. If not set, it defaults to--memoryplus the host’s swap.--kernel-memory: The maximum amount of kernel memory a container can use.
Let’s apply some limits to our my-resource-hog container. We’ll limit it to 1 CPU and 512MB of memory.
# Stop and remove the old container
podman stop my-resource-hog
podman rm my-resource-hog
# Run with limits
podman run -d --name my-limited-hog --cpus 1 --memory 512m python:3.9 python /app/resource_hog.py
Now, if you podman exec my-limited-hog top, you’ll observe that the Python process is capped at using approximately one CPU core and its memory usage will not exceed 512MB (it might hit the limit and then the kernel’s OOM killer might start being invoked within the container’s cgroup, depending on how the application handles it).
When you specify --cpus 1, Podman is essentially setting the cpu.shares equivalent to a value that grants it one core’s worth of processing power, and more directly, setting the cpu.cfs_quota_us and cpu.cfs_period_us in the cgroup. The cfs_period_us is typically 100ms (100000 microseconds), and cfs_quota_us would be set to 100000 microseconds for --cpus 1, meaning it can run for the entire period. For --cpus 0.5, the quota would be 50000 microseconds.
The most surprising thing about Podman resource limits, and cgroups in general, is how they interact with the Linux Out-Of-Memory (OOM) killer. When a container hits its memory limit (--memory), it doesn’t just gracefully stop. Instead, the kernel’s OOM killer is invoked within that container’s cgroup. This means the OOM killer will select a process inside the container to terminate to free up memory, rather than killing a process on the host or terminating the entire container process group if it’s a single process. If the container’s main process is the one consuming memory, it’s likely to be killed.
If you’ve set --cpus and --memory, the next thing you’ll likely encounter is the container being restarted by Podman if its main process is killed by the OOM killer, or the application inside the container exhibiting severe performance degradation due to CPU throttling.