Docker & DevOps

Limit Docker container CPU and memory

Stop one container from slowing down your whole VPS. Set memory and CPU limits with docker run and Docker Compose, watch usage with docker stats and avoid out-of-memory kills.

IntermediateLinux server5 min read

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 answer cgroup2fs means cgroup v2.
  • An idea of how much RAM and how many vCPUs your plan has: run free -h and nproc.

See current usage first

Before you set limits, measure what each container really uses:

Bash
docker stats

It shows a live table with CPU %, memory usage against the limit, network and disk I/O. For a one-time snapshot, add --no-stream:

Bash
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

Bash
docker run -d --name app \
  --memory 512m \
  --memory-swap 512m \
  --cpus 1.5 \
  myapp:1.0
  • --memory 512m is a hard limit. If the container tries to use more, the kernel's OOM killer ends a process inside it.
  • --memory-swap 512m sets the total of memory plus swap. Setting it equal to --memory means the container cannot use swap at all, which keeps behaviour predictable.
  • --cpus 1.5 allows 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:

YAML
services:
  app:
    image: myapp:1.0
    mem_limit: 512m
    memswap_limit: 512m
    cpus: 1.5
    pids_limit: 200

Compose also accepts the deploy.resources form, which is the same syntax Docker Swarm uses:

YAML
services:
  app:
    image: myapp:1.0
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 512M
        reservations:
          memory: 256M

Use 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:

Bash
docker update --memory 768m --memory-swap 768m --cpus 2 app

If 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:

Bash
docker inspect app --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'

The kernel log shows it too:

Bash
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:

Bash
docker inspect app --format 'mem={{.HostConfig.Memory}} cpus={{.HostConfig.NanoCpus}}'
docker stats --no-stream app

mem=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 --memory or 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-compose v1 binary. Use the docker compose plugin. 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.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.