

Sarthak Varshney is a Docker Captain, 5x C# Corner MVP, and 2x Alibaba Cloud MVP, with over six years of hands-on experience in the IT industry, specializing in cloud computing, DevOps, and modern application infrastructure. He is an Author and Associate Consultant, known for working extensively with cloud platforms and container-based technologies in real-world environments.
So you've been running containers for a while now. You know how to spin one up, map ports, maybe even write a Dockerfile. But here's a question — have you ever thought about what happens when your container starts eating up all the RAM on your machine? Or hogs 100% of your CPU and freezes everything else?
Yeah. That's not fun when it happens in production at 2 AM.
This article is about something that most beginners skip over — resource limits in Docker. We're talking about controlling how much memory and CPU a container is allowed to use, and what Docker does when a container crosses the line. It's one of those topics that feels optional until it suddenly feels very, very important.
Let's dig in.
Imagine you have a shared apartment with four roommates. You've all agreed to split the internet bill. Now imagine one roommate starts downloading 4K movies 24/7 and uses up the entire bandwidth for themselves. Everyone else's Netflix starts buffering. Zoom calls break. People get annoyed.
That's exactly what happens on a server when one container has no resource limits. It can grab all the CPU or RAM it wants, and everything else — other containers, the host OS, even your SSH connection — starts suffering.
In real-world Docker setups (especially when you're running multiple services on one machine), you have to set limits. Otherwise you're just hoping everything plays nice, and hope is not a deployment strategy.
docker statsBefore we talk about setting limits, let's talk about measuring things. Docker comes with a super handy command called docker stats. Think of it as your container's activity monitor or task manager.
Run this:
docker stats
You'll see a live, updating table that looks something like this:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
a1b2c3d4e5f6 web-app 12.4% 145MiB / 7.77GiB 1.8% 1.2MB / 300kB 0B / 0B
f6e5d4c3b2a1 database 3.2% 512MiB / 7.77GiB 6.4% 500kB / 1.1MB 8MB / 12MB
Here's what each column means:
Notice something interesting? When you haven't set any limits, the MEM LIMIT column shows your total host RAM. That means the container has permission to use all of it. Not ideal.
If you just want stats for one specific container:
docker stats my-container-name
And if you want a single snapshot instead of a live feed (useful for scripts):
docker stats --no-stream
This command alone is incredibly useful for debugging. If your application is slow, run docker stats and see if something is eating up resources. You'd be surprised how often that's the culprit.
Alright, now let's actually put a leash on these containers.
--memory flagWhen you run a container, you can use the --memory (or -m) flag to cap how much RAM it can use:
docker run -d --memory="512m" --name my-app nginx
This tells Docker: "This container gets a maximum of 512 megabytes of RAM. Not a byte more."
You can use different units:
256m — 256 megabytes1g — 1 gigabyte500000000 — 500 million bytes (just use m and g, honestly)--memory-swap flagHere's one that trips up a lot of beginners. By default, when Docker sets a memory limit, it also allows the container to use the same amount of swap space on top. So if you set --memory="512m", Docker actually allows 512MB RAM + 512MB swap = 1GB total.
If you want to control this explicitly:
docker run -d --memory="512m" --memory-swap="512m" --name my-app nginx
Setting --memory-swap to the same value as --memory means no swap is allowed at all. The container gets exactly 512MB and nothing more.
If you set --memory-swap to -1, you're allowing unlimited swap. That's usually not what you want in production.
After running your container with a memory limit, verify it with:
docker inspect my-app | grep -i memory
You'll see something like:
"Memory": 536870912,
"MemorySwap": 536870912,
That 536870912 is 512MB in bytes. The limit is set.
Or you can just use docker stats and watch the MEM USAGE / LIMIT column — it'll show your configured limit instead of total host RAM now.
Memory limits are pretty straightforward — you're saying "this container can use at most X bytes." CPU limits are a little more nuanced because CPUs work in time slices rather than fixed chunks.
--cpus flagThe simplest way to limit CPU usage:
docker run -d --cpus="1.5" --name my-app nginx
This means the container can use at most 1.5 CPU cores worth of processing time. On a 4-core machine, it can never use more than 37.5% of total CPU capacity, no matter what.
Some practical examples:
--cpus="0.5" — Half a CPU core--cpus="2" — Two full CPU cores--cpus="0.25" — A quarter of a CPU core (useful for lightweight background tasks)--cpu-shares flagThis one works differently — it's about relative priority, not hard limits.
docker run -d --cpu-shares=512 --name low-priority-app nginx
docker run -d --cpu-shares=1024 --name high-priority-app nginx
The default --cpu-shares value is 1024. A container with 512 gets roughly half the CPU time of a container with 1024 — when the system is under load. When the CPU is idle, both containers can still use whatever they need.
Think of it like a queue at a coffee shop. When there's no rush, everyone gets served quickly. But when it's crowded, the VIP members (higher cpu-shares) get served first and more often.
This is useful when you want some services to have higher priority without hard-capping others.
--cpuset-cpusIf your machine has multiple CPU cores and you want a container to run on specific ones:
docker run -d --cpuset-cpus="0,1" --name my-app nginx
This tells Docker: "Only run this container's processes on CPU cores 0 and 1." Useful in advanced scenarios where you want to reduce cache misses or isolate workloads, but you probably won't need this as a beginner.
Here's a more complete example — a web application container with both memory and CPU limits:
docker run -d \
--name production-web \
--memory="256m" \
--memory-swap="256m" \
--cpus="0.5" \
-p 8080:80 \
nginx
This container:
For Docker Compose users, the equivalent looks like this:
version: '3.8'
services:
web:
image: nginx
ports:
- "8080:80"
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
Note: In Docker Compose v3, the deploy.resources section is technically for Swarm mode, but docker compose up on a standalone machine respects it too in recent versions. Alternatively, you can use the mem_limit and cpus keys directly:
services:
web:
image: nginx
mem_limit: 256m
cpus: 0.5
This is the part where things get interesting — and a little brutal, honestly.
When a container tries to use more memory than its limit allows, the Linux kernel's OOM Killer (Out-Of-Memory Killer) steps in. And it does exactly what the name suggests — it kills a process.
Docker relies on Linux cgroups (control groups) to enforce memory limits. When the container's memory usage hits the cap and it tries to allocate more:
You can observe this by running:
docker inspect my-app | grep -i oomkilled
If you see:
"OOMKilled": true
There it is. Your container was killed because it ran out of memory. That's your cue to either increase the memory limit or figure out why your app is consuming so much RAM.
Docker also logs the event. Check with:
docker events --filter container=my-app
You'll see something like oom in the events, which confirms it.
One important thing — the container doesn't restart automatically after an OOM kill unless you've set a restart policy:
docker run -d --memory="256m" --restart=on-failure my-app
With --restart=on-failure, Docker will try to bring the container back up after it gets killed. But if it keeps hitting OOM, it'll keep restarting in a loop — which is its own problem. Fix the root cause.
CPU limits work differently from memory limits. When a container tries to use more CPU than its limit allows, Docker doesn't kill it. Instead, it throttles it — slows it down.
The container's processes are simply made to wait. They get paused when they've used their allocated CPU time, and resumed in the next cycle. From inside the container, it just feels like the app is running slower.
You can actually see this throttling happening by running:
docker stats my-app
If the CPU % is consistently hovering right at the cap (say, 50% on a container set with --cpus="0.5" on a single-core machine), the container is being throttled. It wants more CPU but it's not getting it.
This is usually less catastrophic than OOM kills but can still cause timeouts, slow responses, and degraded performance. If you notice requests to your containerized app are timing out and docker stats shows CPU is maxed out at the limit — bump up the --cpus value.
Mistake 1: Not setting any limits at all
This is the big one. No limits means a misbehaving container can tank your entire machine. Always set sensible limits, especially in production.
Mistake 2: Setting memory limits too low
If you set --memory="64m" for a Java application... it's going to get OOM killed almost immediately. JVMs are hungry. Know your application's actual memory requirements first, then set limits with some breathing room (maybe 20-30% headroom above normal usage).
Mistake 3: Confusing --memory-swap semantics
Remember — --memory-swap is the total of RAM + swap, not swap alone. So if --memory="512m" and --memory-swap="1g", that's 512MB RAM + 512MB swap. A lot of people get this backwards.
Mistake 4: Not monitoring after setting limits
Set limits, then use docker stats to watch your containers for a while. Make sure they're comfortably within bounds during normal operation, and not constantly flirting with the limit.
Mistake 5: Ignoring OOM kills in logs
Containers silently restarting can mask an OOM kill. Always check docker inspect for OOMKilled: true if you notice a container restarting unexpectedly.
Here's a hands-on challenge to cement everything you've learned. Do this on your local machine.
The Challenge: Witness an OOM Kill
Step 1: Pull a simple Python image and run a memory-hungry script inside it with a tight memory limit:
docker run -d \
--name oom-test \
--memory="50m" \
--memory-swap="50m" \
python:3.11-slim \
python -c "
data = []
while True:
data.append('x' * 10**6)
"
Step 2: Open a second terminal and watch it live:
docker stats oom-test
Watch the MEM USAGE climb... and then the container disappears.
Step 3: Check what killed it:
docker inspect oom-test | grep -i oomkilled
You should see "OOMKilled": true. You just witnessed the OOM Killer in action.
Bonus challenge: Now run the same thing but with --memory="500m" and see how long it runs before your host machine starts sweating. (Spoiler: stop it before it actually eats all your RAM — use docker stop oom-test.)
Extra credit: Set up a Docker Compose file with two services — one with a high CPU share and one with a low one. Run a CPU-intensive task in both and observe the difference in docker stats. Which one gets more CPU time?
| Flag | What It Does | Example |
|---|---|---|
--memory | Hard RAM limit | --memory="512m" |
--memory-swap | Total RAM + swap limit | --memory-swap="512m" |
--cpus | Hard CPU core limit | --cpus="1.5" |
--cpu-shares | Relative CPU priority | --cpu-shares=512 |
--cpuset-cpus | Pin to specific cores | --cpuset-cpus="0,1" |
docker stats | Live resource usage | docker stats |
docker stats --no-stream | One-time snapshot | docker stats --no-stream |
Resource limits are one of those things that separate "just tinkering with Docker" from actually running containers responsibly. They protect your machine, protect other services, and give you visibility into how your applications are actually behaving at runtime.
The workflow is simple: use docker stats to understand your baseline, set reasonable --memory and --cpus limits, and know that exceeding memory = OOM kill (bad, fix it) while exceeding CPU = throttling (annoying, but survivable).
Start adding limits to every container you run, even on your local machine. Get into the habit now, and production will thank you later.