Limit container CPU and memory
Restrict CPU and memory usage for Docker containers with practical commands and resource limits.
Focus: limit container cpu and memory
You've deployed a containerized application and it's running smoothly — until a memory leak pushes it over the host's RAM limit, killing not just your app but neighboring containers too. Without resource constraints, a single runaway container can starve the entire system, leading to unpredictable failures and frustrated teammates. Learning to limit container CPU and memory is essential to building robust, multi-tenant Docker environments.
The problem this lesson solves
Imagine sharing a house with roommates who never cap their usage — one person blasts the AC while another runs every appliance at once. In Docker, each container shares the host kernel's CPU and memory. Without explicit limits, a container can:
- Consume 100% of CPU, starving other processes and slowing the host.
- Leak memory until the OOM killer terminates it — or worse, kills other critical containers.
- Cause unpredictable performance in production clusters where resource isolation is required.
This is why every serious Docker deployment uses resource constraints via --memory and --cpus flags. The Docker engine uses Linux cgroups (control groups) to enforce these limits, ensuring fair scheduling and preventing noisy neighbors.
Core concept / mental model
Think of the host machine as a shared office kitchen. Without rules, one person might use the microwave all day, leaving others cold. CPU limits are like booking time slots — a container is guaranteed not to exceed its allocated share. Memory limits are like setting a maximum capacity for each employee's fridge shelf — if someone exceeds their space, their items get cleared out.
Technically, Docker wraps Linux cgroups v1/v2 and kernel OOM score adjustment to enforce two types of constraints:
| Resource | Mechanism | Default (no limit) |
|---|---|---|
| Memory | memory.max_in_bytes (cgroup v2) or memory.limit_in_bytes (v1) |
Host's total RAM — dangerous |
| CPU | cpu.max (cgroup v2) or cpu.shares (v1) with --cpus |
All available cores — no limit |
💡 Key Insight:
--memoryis a hard limit — if a process exceeds it, the kernel kills it.--cpusis a soft scheduling cap — a container won't use more CPU than specified, but may use less if free.
How it works step by step
1. Allocate no more than N megabytes of memory
The --memory (or -m) flag sets a hard cap on RAM + swap. If a process inside the container tries to allocate more, it gets an OutOfMemory error and the container exits (or is killed by Docker).
# Limit to 512 MB of RAM (swap also capped by default)
docker run -d --memory=512m --name limited-redis redis:alpine
2. Reserve minimum memory guarantees
Use --memory-reservation to set a soft warning threshold. Docker tries to keep usage under this value; if the host runs low, it can reclaim memory from containers exceeding their reservation.
# Soft limit at 256 MB, hard limit at 512 MB
docker run -d --memory=512m --memory-reservation=256m nginx
3. Constrain CPU to a fraction of a core
The --cpus flag specifies how many CPU cores the container can use. A value of 0.5 means it can use up to half of one core (500 millicores).
# Limit to 0.5 CPU core
docker run -d --cpus=0.5 --name cpu-aware busybox stress --cpu 1
4. View current limits
Check running containers with docker inspect or docker stats:
docker stats --no-stream
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT
# a1b2c3d4 limited-redis 0.02% 4.2MiB / 512MiB
Hands-on walkthrough
Let's create two containers — one with limits, one without — and observe the difference under load.
Step 1: Start a limited container
# Run a stress tool with memory and CPU limits
docker run -d --memory=256m --cpus=0.5 --name stressed alpine sleep 3600
# Install stress in the container (inside)
docker exec stressed apk add stress-ng
Step 2: Generate CPU load
docker exec stressed stress-ng --cpu 4 --timeout 30s &
Check docker stats --no-stream — CPU % should hover around 50% (0.5 core).
Step 3: Generate memory pressure
docker exec stressed stress-ng --vm 1 --vm-bytes 200M --timeout 10s &
Watch docker stats — memory stays near 200MB, well under the 256MB limit. Now try allocating 300MB:
docker exec stressed stress-ng --vm 1 --vm-bytes 300M --timeout 5s
# This likely crashes the container
Run docker ps -a to see the container is now in Exited (137) state — 137 = SIGKILL from OOM killer.
Step 4: Clean up
docker rm -f stressed
Compare options / when to choose what
| Approach | Use case | Trade-off |
|---|---|---|
--memory only |
Simple hard limit | No CPU isolation — memory leak kills container |
--cpus only |
CPU-bound workloads | Memory can still balloon |
| Both together | Production workloads | Most predictable — but need to tune |
--memory-reservation |
Mixed-overcommit environments | Soft guarantee — host may ignore on contention |
--cpuset-cpus |
Pin to specific cores (e.g., 0-1) | Reduces CPU migration overhead — less flexible |
When to use what: Start with
--memoryand--cpustogether. Add--memory-reservationif you want over-commit tolerance. Use--cpuset-cpusonly for low-latency real-time workloads.
Troubleshooting & edge cases
Container exits with code 137
This indicates an OOM kill. Check docker logs <container> — you'll see "Killed" or empty output if the OOM killer struck.
Fix: Increase --memory or refactor the application to use less memory.
Container starts but uses more CPU than allowed
This might happen with --cpu-shares (old syntax) instead of --cpus. Use --cpus for exact fractions.
# Wrong: --cpu-shares 512 is a relative weight, not a hard cap
docker run --cpu-shares 512 ...
# Correct: --cpus=0.5 gives precise 50% of one core
docker run --cpus=0.5 ...
Memory limit too low for database workloads
Databases (PostgreSQL, MySQL) often ignore --memory if they can't allocate shared buffers. Always test under realistic load.
Swap adds hidden memory
By default, --memory limits RAM + swap. To disable swap mappings: --memory-swap=0 (or set equal to --memory).
# Disable swap for critical containers
docker run --memory=256m --memory-swap=256m nginx
What you learned & what's next
You now know how to limit container CPU and memory using --cpus, --memory, and --memory-reservation. You've seen:
- How Docker enforces limits via cgroups (mental model of shared resource scheduling).
- Step-by-step commands to create constrained containers.
- How to detect and fix OOM kills (exit code 137).
- When to use soft vs hard limits.
The next lesson builds on this by exploring Docker Compose resource constraints — defining per-service limits in YAML for entire multi-container applications. Master that, and you'll be ready to deploy stable microservices on any platform.
Practice recap
Run a Python Flask app in a container with --memory=256m --cpus=0.5. Use docker stats to monitor its usage, then install stress-ng inside and attempt to exceed the memory limit. Observe the container exit with code 137. Repeat with --memory-reservation=128m and see how Docker handles soft limits.
Common mistakes
- Setting
--memorywithout--cpus— a memory leak still kills the container, but CPU-bound workloads can starve other containers. - Using
--cpu-sharesthinking it's a hard limit — it's a relative weight, not a cap. Use--cpusinstead. - Forgetting that
--memoryincludes swap by default — your container might think it has more RAM than allowed, leading to unexpected OOM kills. - Setting limits too low for stateful services (databases, queues) without testing under real load — they may fail silently during peak usage.
Variations
- Use
--cpuset-cpusto pin a container to specific CPU cores (e.g.,--cpuset-cpus=0-1) for latency-sensitive workloads. - Set pids limit with
--pids-limit=100to prevent fork bombs — complements CPU/memory constraints. - In Kubernetes, use
resources.requestsandresources.limitsinstead of Docker flags — same cgroup model but YAML-based.
Real-world use cases
- A Node.js API server is constrained to 512 MB RAM and 0.5 CPU to prevent memory leaks from crashing the host during high traffic.
- A batch processing job limited to 2 CPUs and 4 GB RAM runs alongside interactive web containers — ensuring fair scheduling on a shared VM.
- A IoT gateway container is pinned to CPU core 0 with
--cpuset-cpus=0and--memory=128mto guarantee low-latency sensor data processing.
Key takeaways
- Use
--cpus(not--cpu-shares) to set hard CPU limits — it's fractional and precise. --memoryis a hard cap including swap unless you set--memory-swapseparately.- Exit code 137 means the OOM killer terminated the container — always increase
--memoryor fix the leak. - Combine
--memoryand--cpustogether for production-ready resource isolation. - Test resource limits with
stress-ngor a similar tool before deploying in production.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.