Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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: --memory is a hard limit — if a process exceeds it, the kernel kills it. --cpus is 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 --memory and --cpus together. Add --memory-reservation if you want over-commit tolerance. Use --cpuset-cpus only 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 --memory without --cpus — a memory leak still kills the container, but CPU-bound workloads can starve other containers.
  • Using --cpu-shares thinking it's a hard limit — it's a relative weight, not a cap. Use --cpus instead.
  • Forgetting that --memory includes 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

  1. Use --cpuset-cpus to pin a container to specific CPU cores (e.g., --cpuset-cpus=0-1) for latency-sensitive workloads.
  2. Set pids limit with --pids-limit=100 to prevent fork bombs — complements CPU/memory constraints.
  3. In Kubernetes, use resources.requests and resources.limits instead 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=0 and --memory=128m to guarantee low-latency sensor data processing.

Key takeaways

  • Use --cpus (not --cpu-shares) to set hard CPU limits — it's fractional and precise.
  • --memory is a hard cap including swap unless you set --memory-swap separately.
  • Exit code 137 means the OOM killer terminated the container — always increase --memory or fix the leak.
  • Combine --memory and --cpus together for production-ready resource isolation.
  • Test resource limits with stress-ng or a similar tool before deploying in production.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.