By default a container can use all of the server's CPU and memory. On a small VPS that means one memory leak or one busy job can make SSH slow, push the system into swap or get an important process killed. Resource limits put a ceiling on each container. This guide shows how to set them, how to choose sensible values and how to tell when a limit is too tight.
Before you start
- A Linux VPS with Docker Engine and the Compose plugin.
- Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 9. These use cgroup v2, which Docker's limits rely on. Check with
stat -fc %T /sys/fs/cgroup/; the answercgroup2fsmeans cgroup v2. - An idea of how much RAM and how many vCPUs your plan has: run
free -handnproc.
See current usage first
Before you set limits, measure what each container really uses:
docker statsIt shows a live table with CPU %, memory usage against the limit, network and disk I/O. For a one-time snapshot, add --no-stream:
docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}'Watch it for a while under normal load, and ideally during a busy period, so your limits leave headroom.
Note: CPU % in docker stats is relative to one core. A container using two full cores shows 200%.
Set limits with docker run
docker run -d --name app \
--memory 512m \
--memory-swap 512m \
--cpus 1.5 \
myapp:1.0--memory 512mis a hard limit. If the container tries to use more, the kernel's OOM killer ends a process inside it.--memory-swap 512msets the total of memory plus swap. Setting it equal to--memorymeans the container cannot use swap at all, which keeps behaviour predictable.--cpus 1.5allows at most one and a half cores of CPU time. The container is slowed down, not killed, when it hits this.
Other useful options:
--memory-reservation 256m: a soft target the kernel tries to reclaim down to when the host is short of memory.--pids-limit 200: caps the number of processes, which protects against fork bombs.--cpu-shares 512: a relative weight that only matters when containers compete for CPU. The default is 1024.
Set limits in Docker Compose
The simplest way, understood by Docker Compose v2:
services:
app:
image: myapp:1.0
mem_limit: 512m
memswap_limit: 512m
cpus: 1.5
pids_limit: 200Compose also accepts the deploy.resources form, which is the same syntax Docker Swarm uses:
services:
app:
image: myapp:1.0
deploy:
resources:
limits:
cpus: "1.5"
memory: 512M
reservations:
memory: 256MUse one form per service, not both. Apply the change with docker compose up -d.
Change limits on a running container
docker update changes limits without a restart:
docker update --memory 768m --memory-swap 768m --cpus 2 appIf the container is managed by Compose, put the same values in the file, or the next deploy will undo them.
Choose sensible values
- Leave room for the system itself: SSH, the Docker daemon, the firewall and any host services. On a 2 GB VPS, keep at least a few hundred megabytes free outside all limits.
- Give databases enough memory for their cache, and set the database's own memory settings to match the limit. A database that thinks it has the whole server will be OOM-killed under a 512 MB limit.
- Runtimes such as the JVM and Node.js have their own heap settings. Recent versions read the container limit, but setting the heap explicitly (for example
NODE_OPTIONS=--max-old-space-size=384) avoids surprises. - Limits can add up to more than the server has. That is fine for CPU, which is shared, but risky for memory if every container peaks at once.
Detect and avoid OOM kills
When a container is killed for exceeding its memory limit, it exits with code 137 and Docker records it:
docker inspect app --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'The kernel log shows it too:
sudo dmesg -T | grep -i -E 'killed process|oom'If you see OOM kills, either raise the limit, lower the app's own memory use (workers, cache sizes, heap), or both. Adding swap to the host can help the system survive spikes; see Add a swap file on Linux.
Check it works
Confirm the limits are in place:
docker inspect app --format 'mem={{.HostConfig.Memory}} cpus={{.HostConfig.NanoCpus}}'
docker stats --no-stream appmem=536870912 is 512 MB in bytes, and cpus=1500000000 is 1.5 CPUs. In docker stats, the MEM USAGE / LIMIT column should show / 512MiB instead of the total RAM.
Troubleshooting
- Container keeps restarting with exit code 137: it is being OOM-killed. Raise
--memoryor reduce the app's memory settings. WARNING: Your kernel does not support swap limit capabilities: the host is on cgroup v1 or swap accounting is off. Memory limits still work; swap limits do not.- App is slow but not using much memory: it may be hitting the CPU limit. Look for CPU % pinned at the limit in
docker stats. - Limits ignored in Compose: you are using a very old
docker-composev1 binary. Use thedocker composeplugin. For overall high usage, see Fix high CPU or memory use in Docker containers.
Next steps
- Track resource usage over time: Monitor Docker containers with cAdvisor.
- Find what is eating resources: Fix high CPU or memory use in Docker containers.
