How do you limit a container's CPU and memory usage?
Quick Answer
--memory sets a hard memory limit, beyond which the container's process is killed by the kernel's OOM killer — the same underlying mechanism covered in the Kubernetes stack. --cpus sets a fractional CPU limit (e.g., --cpus=1.5 for one and a half cores' worth of CPU time), enforced via the Linux kernel's CFS quota mechanism. The container is throttled, not killed, if it tries to exceed this. Both flags translate directly into the same cgroup configuration Kubernetes and other orchestrators use under the hood.
Detailed Answer
Setting a memory limit
docker run --memory="512m" myapp
This directly configures a cgroup memory limit. If the container's processes try to use more than 512MB total, the kernel's OOM killer terminates the offending process. Kubernetes uses this same underlying mechanism, with the same tradeoffs, for its own memory limits and OOMKilled behavior. Memory can't be gracefully throttled the way CPU can — there's no way to make an over-budget memory allocation just run more slowly. So exceeding this limit always means termination, not degradation.
docker run --memory="512m" --memory-swap="1g" myapp
--memory-swap sets the combined memory-plus-swap limit. If you don't set it, it defaults to double the --memory value, allowing some swap usage as a buffer. Setting --memory-swap equal to --memory disables swap for the container entirely.
Setting a CPU limit
docker run --cpus="1.5" myapp
--cpus caps the container at that many CPU cores' worth of processing time. Fractional values are allowed — 1.5 means one and a half cores. The kernel enforces this with the Completely Fair Scheduler (CFS). Unlike memory, exceeding a CPU limit doesn't kill the container — the kernel simply throttles it. The container gets less CPU time than it's trying to use, so it keeps running, just more slowly.
docker run --cpu-shares=512 myapp
--cpu-shares is an older mechanism. Instead of an absolute cap, it sets a relative weight for CPU time when the host's CPU is under contention. A container with --cpu-shares=1024 gets twice the CPU time of one with --cpu-shares=512, but only when the host's CPU is actually contended. If the host isn't contended, either container can use up to 100% of available CPU. This is a different model from --cpus's hard cap — the two are sometimes used together: a relative priority under contention, plus an absolute ceiling.
Why exceeding these limits behaves so differently
Memory limits cause termination. CPU limits cause throttling. This comes from the nature of each resource: memory is incompressible, a process either has an allocation or it doesn't, while CPU time is compressible, a process can simply be scheduled less often and run more slowly without crashing. Both mechanisms rely on the same underlying Linux cgroups. Kubernetes's resources.limits are, at the implementation level, just a more structured, orchestrator-managed way of setting these same --memory/--cpus-equivalent cgroup constraints.
Inspecting current resource usage
docker stats
# CONTAINER CPU % MEM USAGE / LIMIT MEM %
# my-api 12.34% 340MiB / 512MiB 66.4%
docker stats gives a live view of actual resource consumption against configured limits. Use it to confirm a configured limit is realistic — one that sits comfortably above typical usage, with headroom. Check this before a real incident forces the question, not after.
Why setting these limits matters, even on a single host
Without explicit limits, a single misbehaving or leaking container can consume all of a host's available CPU or memory, starving every other container sharing that machine. This is the same "noisy neighbor" problem that affects any shared infrastructure, just at the scale of one Docker host instead of a whole cluster. Setting sensible --memory and --cpus limits on every container is basic operational hygiene — true even in a simple single-host Docker Compose setup, not just in orchestrated, multi-node environments.