
August 21, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Unchecked containers are the most common cause of unexpected server outages in shared hosting environments. When you limit Docker container resources correctly, you prevent a single runaway PHP-FPM process or Node.js worker from starving your database and crashing the entire host. This guide covers the exact CLI flags, Compose configurations, and Cgroup v2 mechanics required to enforce hard boundaries on production Linux servers running Ubuntu 22.04 or 24.04.
--memory for RAM caps and --cpus for processor quotas in docker run. In Docker Compose, define these under deploy.resources.limits. Always set soft reservations alongside hard limits to guarantee baseline performance without triggering OOM kills during normal operation.How do you limit Docker container resources using the CLI?
The most direct way to constrain a container is at runtime using docker run flags. These map directly to Linux Control Groups (cgroups). On modern systems running Cgroup v2 (standard on Ubuntu 22.04+), these limits are enforced strictly by the kernel. If you are managing infrastructure for DevOps automation, understanding these primitives is essential before abstracting them into orchestration tools.
Setting Hard Memory Limits
The --memory (or -m) flag sets a hard ceiling. If the container exceeds this, the kernel invokes the OOM killer. For a Laravel application running PHP-FPM, a typical starting point might be 512MB to 1GB depending on worker count.
<!-- Run a Laravel app with strict 1GB RAM limit and 1.5 CPU quota -->
docker run -d \
--name laravel-app \
--memory=1g \
--memory-swap=1g \
--cpus=1.5 \
my-laravel-image:latest - --memory: The maximum amount of RAM the container can use.
- --memory-swap: Total memory (RAM + swap). Set equal to
--memoryto disable swap entirely. Disabling swap is usually preferred for web apps to avoid silent performance degradation. - --oom-kill-disable: Prevents the kernel from killing the container when it hits the limit. Use with extreme caution; the process will simply hang instead of restarting.
Configuring CPU Quotas
CPU limiting works differently than memory. Instead of a hard cap, Docker uses a quota system based on the Completely Fair Scheduler (CFS). The --cpus flag is the user-friendly wrapper introduced in recent Docker versions. A value of 1.0 equals one full core's worth of execution time per scheduling period.
You can also pin containers to specific physical cores using --cpuset-cpus="0,2". This is valuable on multi-tenant servers where you want to isolate noisy workloads from latency-sensitive databases. For teams evaluating cloud hosting providers, note that some VPS instances have non-uniform NUMA architectures where core pinning significantly impacts throughput.
How do you configure resource limits in Docker Compose?
In production, you rarely run raw docker run commands. Docker Compose is the standard for defining reproducible stacks. However, the syntax changed significantly between Compose V2/V3 and the current specification. As of 2026, with Docker Engine 27.x and Compose V2 integrated, you should use the deploy key even in standalone mode.
services:
app:
image: my-laravel-app:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
redis:
image: redis:7.4-alpine
deploy:
resources:
limits:
memory: 256M
cpus: '0.5' Limits vs Reservations
This distinction causes more confusion than any other Docker configuration. Understanding it prevents both waste and instability:
- Limits: The absolute maximum. The kernel enforces this. Exceeding memory triggers OOM kill; exceeding CPU causes throttling.
- Reservations: A guaranteed minimum. The scheduler ensures this capacity is available before allocating to other containers. It does NOT reserve exclusive access if the container isn't using it.
On a server with 8GB RAM hosting multiple services, set reservations to what the app needs during idle/low traffic, and limits to what it needs during peak bursts. This allows efficient bin-packing while preventing catastrophic contention. For complex applications like those built with Laravel frameworks, profile your memory usage under load before setting these values.
What is the difference between Cgroup v1 and v2 for Docker?
If you are maintaining older servers or migrating legacy infrastructure, you must understand which cgroup version your host uses. Cgroup v2 has been the default on Ubuntu since 21.10 and RHEL since 8. In 2026, virtually all supported LTS distributions use v2 exclusively.
| Feature | Cgroup v1 | Cgroup v2 |
|---|---|---|
| Hierarchy | Multiple independent hierarchies | Single unified hierarchy |
| Memory Accounting | Separate kmem/slab counters | Unified memory.events |
| Pressure Monitoring | Not available natively | Native PSI (Pressure Stall Information) |
| Docker Flag Compatibility | All legacy flags work | All standard flags work; some deprecated |
| IO Throttling | blkio controller | io.controller (unified) |
The practical impact for developers is minimal unless you are writing custom monitoring tooling. Docker abstracts the differences. However, if you see warnings about "cgroup v1 deprecation" during builds or startup, plan an OS upgrade. Newer kernels optimize v2 scheduling paths, meaning identical limits often perform better on v2 hosts.
How do you monitor and verify Docker resource limits in production?
Setting limits is only half the job. You must verify they are active and observe behavior under load. A common mistake I've encountered on client projects is configuring limits in Compose but forgetting to redeploy, leaving containers running with old unrestricted settings.
Inspecting Active Limits
Use docker inspect to confirm the kernel-enforced values. Do not rely on the Compose file alone.
# Check memory limit in bytes
docker inspect --format='{{.HostConfig.Memory}}' laravel-app
# Check CPU quota and period
docker inspect --format='Quota: {{.HostConfig.CpuQuota}}, Period: {{.HostConfig.CpuPeriod}}' laravel-app
# Human-readable summary
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}" If Memory returns 0, no limit is set. If CpuQuota is 0, CPU is unconstrained. Always validate after deployment.
Understanding OOM Kill Events
When a container hits its memory limit, the kernel kills processes inside it. Docker reports this as exit code 137. Monitor this explicitly:
# Check if container was OOM killed
docker inspect --format='{{.State.OOMKilled}}' laravel-app
# View kernel OOM messages
sudo dmesg -T | grep -i "killed process" | tail -20
# Check systemd journal for cgroup events
journalctl -u docker.service | grep "memory: usage" If you see frequent OOM kills, either increase the limit or fix the application leak. For PHP applications, check pm.max_children and memory_limit in php-fpm.conf — these must align with container limits. Setting PHP memory_limit higher than container memory guarantees eventual crashes.
How do you limit Docker disk I/O and network bandwidth?
CPU and memory get most attention, but I/O starvation causes just as many production incidents. A backup job or log rotation script can saturate disk bandwidth, making your web application unresponsive even if CPU and RAM are fine.
Disk I/O Throttling
Use --device-read-bps and --device-write-bps to cap throughput per device. Identify the device path first with lsblk.
# Limit write speed to 50MB/s on /dev/sda
docker run -d \
--device-write-bps /dev/sda:50mb \
--device-read-bps /dev/sda:100mb \
my-backup-service:latest In Compose, this maps to blkio_config under the service definition. Note that I/O limits apply per-device, not globally. On NVMe systems with multiple namespaces, identify the correct path carefully.
Network Bandwidth Constraints
Docker does not natively support network bandwidth limiting via CLI flags. This requires either:
- tc (traffic control): Apply qdisc rules to the container's veth interface. Complex but native.
- Wondershaper / trickle: Run inside the container to self-limit.
- Kubernetes/Orchestrator: If using K8s, CNI plugins like Cilium support egress/ingress rate limiting natively.
For most standalone Docker deployments, network saturation is less common than disk I/O. Focus on disk limits first unless you're running bandwidth-intensive media processing or proxy services.
Practical Resource Allocation Strategy
Theory matters less than repeatable patterns. Here is a baseline allocation strategy I use for typical LAMP/LEMP stacks on a 4-core, 8GB VPS. Adjust proportionally for your hardware.
- Database (MySQL/PostgreSQL): Reserve 30-40% of total RAM. Set hard limit to 50%. Database performance degrades catastrophically when swapped. Never starve this tier.
- Application (PHP-FPM/Node): Limit to 25-30% of RAM. Scale horizontally (more containers) rather than vertically (bigger container) when possible.
- Cache (Redis/Memcached): Hard limit to 10-15%. Redis evicts keys when hitting maxmemory; container OOM kills are worse than graceful eviction.
- Background Workers (Queues/Cron): Strict CPU limits (0.5-1.0 core). These are batch workloads that should yield to interactive requests.
- Reverse Proxy (Nginx/Traefik): Minimal limits (256MB, 0.5 CPU). Proxies are lightweight but critical; don't over-constrain.
Always leave 10-15% of host resources unallocated. The kernel needs breathing room for filesystem caches, network buffers, and SSH sessions. Running at 100% utilization means zero margin for spikes.
Conclusion
To effectively limit Docker container resources, combine hard limits with sensible reservations, verify enforcement with docker inspect, and monitor OOM events proactively. The configuration is straightforward; the discipline of profiling, testing, and iterating is what separates stable production systems from fragile ones. Whether you're running legal-tech portals, eCommerce platforms, or SaaS APIs, predictable resource isolation is foundational infrastructure work.
If you need help auditing your container configurations or designing a resilient deployment architecture for your production environment, reach out through my contact page. I regularly help teams stabilize their Docker deployments and optimize resource allocation for real-world workloads.

