Resource Limits (CPU and Memory)
Resource limits control how much CPU and memory a container is allowed to use. They matter because a container is just a process on a host: without limits, one busy or broken workload can starve other containers, the Docker daemon, or your own machine.
Docker applies these limits at container creation time. You can set them with docker run flags for individual containers or with Compose service settings for repeatable local and server deployments.
Overview: How Resource Limits Work
A Docker image is a read-only template made from filesystem layers and image metadata. A container is an instance of that image with a thin writable layer, runtime configuration, mounts, networking, and one or more running processes. CPU and memory limits are not part of the image layers. They are runtime rules attached to the container when Docker creates it.
On Linux, Docker enforces resource limits with kernel control groups, usually called cgroups. Cgroups let the kernel account for and restrict process groups. Docker places the container’s processes into cgroups and configures values such as memory maximums, CPU quota, CPU period, and relative CPU weight. On Docker Desktop for macOS and Windows, Linux containers run inside a managed Linux VM, so the limits apply inside that VM. Docker Desktop also has a separate VM-wide CPU and memory allocation; a container cannot use more than the VM has available.
CPU limits are often expressed in two different ways. A hard-ish quota such as --cpus 1.5 says the container may use up to one and a half CPU cores worth of time across the scheduling period. A relative weight such as --cpu-shares 512 says how CPU should be divided when multiple containers compete, but it does not cap a container when the host is otherwise idle. This difference is important: --cpus is a limit, while --cpu-shares is a priority hint under contention.
Memory limits are more abrupt. With --memory 256m, Docker configures a maximum amount of memory for the container’s cgroup. If processes inside the container exceed the allowed memory and cannot reclaim enough, the kernel may terminate one of them with an out-of-memory kill. From the application’s point of view, that can look like a sudden crash. Containers do not automatically become safer because they are isolated; they still need realistic memory sizing, logs, metrics, and restart behavior.
Resource limits also affect how applications see their environment. Modern runtimes such as recent Java, Node.js, Go, and .NET are much better at noticing cgroup limits than older versions, but you should still test. A process that assumes host-sized memory inside a tiny container can allocate too aggressively and crash. For production services, combine Docker limits with application-level settings such as worker counts, heap sizes, request timeouts, and queue backpressure.
Syntax
docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
| Option | Purpose |
|---|---|
--cpus N |
Limits usable CPU time to N CPUs, such as 0.5, 1, or 2.5. |
--cpu-shares N |
Sets relative CPU weight when containers compete. The default is commonly 1024. |
--memory SIZE or -m SIZE |
Sets a hard memory limit, such as 128m, 512m, or 2g. |
--memory-swap SIZE |
Sets the combined memory plus swap limit. Used with --memory. |
--oom-kill-disable |
Disables the kernel OOM killer for the container. Use with extreme care and normally only with a memory limit. |
docker update |
Changes supported limits on an existing container, though recreating from Compose or infrastructure code is usually clearer. |
Sizes use suffixes such as m for megabytes and g for gigabytes. Put Docker flags before the image name. Anything after the image name is the command that runs inside the container, not a Docker option.
Compose uses service-level settings. For local docker compose usage, common keys include cpus and mem_limit. The older standalone docker-compose command is not used in this course.
Examples
Example 1: Limit a Container to Half a CPU
docker run --rm --name cpu-demo --cpus 0.5 alpine:3.20 sh -c 'echo "CPU limit configured"'
Output:
CPU limit configured
This container is allowed to consume about half of one CPU core over time. It may briefly run on different host cores because Docker does not pin it to one physical CPU by default. The limit is about total scheduled CPU time, not about which specific core it uses.
Example 2: Inspect CPU and Memory Settings
docker run -d --name limited-nginx --cpus 1.5 --memory 256m nginx:1.27-alpine
docker inspect limited-nginx --format 'NanoCpus={{.HostConfig.NanoCpus}} Memory={{.HostConfig.Memory}}'
docker stop limited-nginx
docker rm limited-nginx
Output:
3f6c9d6e5b4a8c2f1a0b9d8e7c6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8
NanoCpus=1500000000 Memory=268435456
limited-nginx
limited-nginx
--cpus 1.5 becomes 1500000000 nanocpus in Docker’s container configuration. --memory 256m becomes 268435456 bytes. The image itself is unchanged; these settings belong to the created container.
Example 3: Disable Swap for a Memory-Limited Container
docker run -d --name memory-demo --memory 128m --memory-swap 128m nginx:1.27-alpine
docker inspect memory-demo --format 'Memory={{.HostConfig.Memory}} MemorySwap={{.HostConfig.MemorySwap}}'
docker stop memory-demo
docker rm memory-demo
Output:
7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4
Memory=134217728 MemorySwap=134217728
memory-demo
memory-demo
When --memory and --memory-swap are the same value, the container cannot use extra swap beyond its memory limit. Swap behavior differs by host configuration, and Docker Desktop adds the VM layer, so test under the same environment where the workload will run.
Example 4: Resource Limits in Compose
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
cpus: 0.50
mem_limit: 256m
Output:
web-1 | /docker-entrypoint.sh: Configuration complete; ready for start up
This Compose service publishes Nginx on host port 8080 and creates the container with half a CPU and 256 MB of memory. The ports entry is what publishes the port; an image’s EXPOSE metadata alone does not make a service reachable from the host.
How It Works Step by Step
- The Docker CLI parses flags such as
--cpusand--memory, then sends a create-container request to the Docker daemon. - The daemon checks whether the image exists locally. If needed, it pulls the image manifest, configuration, and missing read-only layers from a registry.
- Docker creates the container object, including host configuration values for CPU quota, CPU shares, memory, swap, mounts, networking, and restart policy.
- The storage driver mounts the image’s read-only layers and adds the container’s thin writable layer. Resource limits do not create filesystem layers.
- Before the container process starts, Docker asks the Linux kernel to place the process into cgroups configured with the requested limits.
- The kernel scheduler accounts for CPU time and throttles the container when it exceeds its quota during a scheduling period.
- The kernel memory controller tracks memory charged to the container. If the workload exceeds the limit and memory cannot be reclaimed, the kernel can kill a process in that cgroup.
- When the container exits, the limits disappear with that container. The image remains available for future containers with different limits.
You can change some limits with docker update, but repeatability usually matters more than live tweaking. In day-to-day work, edit the command, Compose file, or deployment configuration, then recreate the container so the running state matches the recorded configuration.
Common Mistakes
Using CPU Shares as a Hard Limit
docker run -d --name worker --cpu-shares 256 alpine:3.20 sh -c 'while true; do :; done'
This is not a hard CPU cap. It only lowers the container’s relative weight when CPU is contested. If the host is idle, the process can still use a large amount of CPU. Use --cpus when you need an actual limit:
docker run -d --name worker-limited --cpus 0.25 alpine:3.20 sh -c 'while true; do :; done'
Setting a Memory Limit Without Thinking About OOM Behavior
docker run -d --name tiny-web --memory 32m nginx:1.27-alpine
This may start, but it gives the service very little room for runtime memory, buffers, temporary allocations, and traffic spikes. If the process exceeds the limit, it may be killed. Start with a realistic limit, watch metrics, and leave headroom:
docker run -d --name sized-web --memory 256m nginx:1.27-alpine
Forgetting Docker Desktop’s VM Limit
docker run -d --name big-service --memory 12g nginx:1.27-alpine
On Docker Desktop, Linux containers run inside a VM. If Docker Desktop is configured with less memory than the container asks for, the workload cannot actually use that amount. Raise the Docker Desktop resource allocation or choose a container limit that fits the VM.
Putting Flags After the Image Name
docker run nginx:1.27-alpine --memory 256m
This is wrong because --memory 256m appears after the image name, so Docker treats it as part of the command inside the container. Put runtime flags before the image:
docker run -d --name web-right --memory 256m nginx:1.27-alpine
Best Practices
- Set memory limits for long-running services so one container cannot consume the host or Docker Desktop VM.
- Use
--cpusfor predictable CPU caps; use--cpu-sharesonly for relative priority under contention. - Choose memory limits from measurement, not guesses. Watch real workload behavior with logs, metrics, and load tests.
- Leave headroom for startup spikes, TLS buffers, caches, garbage collection, and occasional larger requests.
- Use specific image tags such as
nginx:1.27-alpineandalpine:3.20;latestis a moving target and weakens reproducibility. - Do not disable the OOM killer unless you understand the host-level risk and have a memory limit in place.
- Record limits in Compose or deployment configuration instead of relying on one-off terminal history.
- Remember that
EXPOSEis documentation metadata only. Use-por Composeportsto publish ports. - Pair Docker limits with application settings such as worker counts, heap maximums, request limits, and queue sizes.
- Test limits on the same platform class you will deploy to, especially when moving between native Linux and Docker Desktop.
Practice Exercises
- Run an
nginx:1.27-alpinecontainer namedpractice-limitswith--cpus 0.75and--memory 192m. Inspect it and confirm the configured nanocpus and memory bytes. - Create a Compose file for a web service using
nginx:1.27-alpine, publish host port8090to container port80, and setcpusto0.50andmem_limitto256m. - Start two CPU-bound Alpine containers, one with
--cpus 0.25and one with--cpus 1.0. Hint: use a simple shell loop and compare their CPU usage withdocker stats, then stop and remove both containers.
Summary
- Docker resource limits are runtime configuration attached to containers, not image layers.
- Linux containers are enforced with cgroups; Docker Desktop applies those limits inside its Linux VM.
--cpuscaps CPU time, while--cpu-sharessets relative priority during contention.--memorysets a memory ceiling, and exceeding it can cause an out-of-memory kill.- Use Compose
cpusandmem_limitwhen you want repeatable service configuration. - Put Docker runtime flags before the image name, use specific image tags, and test limits with realistic workloads.
