Configure container resource limits with Docker and systemd for production workloads

Intermediate 25 min Apr 29, 2026 96 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Learn to set memory, CPU, and I/O limits for Docker containers using both Docker runtime constraints and systemd service limits. Master cgroups v2 integration for bulletproof resource management in production environments.

Prerequisites

  • Root or sudo access
  • Docker installed and running
  • systemd-based Linux distribution
  • Basic familiarity with container concepts

What this solves

Without proper resource limits, a single container can consume all available system memory or CPU, causing performance degradation or system crashes. This tutorial shows you how to configure both Docker-native resource constraints and systemd service limits to ensure containers stay within their allocated resources, preventing resource starvation and improving system stability.

Understanding container resource management

Resource limits work at multiple layers in a containerized environment. Docker applies constraints through the container runtime, while systemd manages the Docker daemon itself. Modern Linux systems use cgroups v2 for unified resource control, providing better accounting and enforcement than the previous cgroups v1 implementation.

The key difference: Docker limits constrain individual containers, while systemd limits constrain the entire Docker service. You need both for comprehensive protection. A runaway container might respect Docker limits but still overwhelm the system if the Docker service itself has no limits.

Step-by-step configuration

Verify cgroups v2 support

Check that your system uses cgroups v2, which provides unified resource management and better container integration.

mount | grep cgroup
cat /proc/cgroups

You should see cgroup2 mounted at /sys/fs/cgroup. If you see separate cgroups v1 mounts, you may need to enable cgroups v2.

Enable cgroups v2 if needed

For systems still using cgroups v1, enable v2 by updating the kernel boot parameters.

sudo nano /etc/default/grub
GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1 cgroup_no_v1=all"
sudo update-grub
sudo reboot

Configure Docker daemon resource limits

Set systemd limits for the Docker service to prevent the daemon from consuming excessive resources.

sudo systemctl edit docker
[Service]
MemoryAccounting=true
MemoryLimit=8G
CPUAccounting=true
CPUQuota=400%
TasksAccounting=true
TasksMax=4096

This limits Docker service to 8GB RAM, 4 CPU cores worth of processing time, and maximum 4096 tasks. Adjust values based on your system capacity.

Reload systemd and restart Docker

Apply the new service limits by reloading systemd configuration and restarting Docker.

sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl status docker

Configure Docker container memory limits

Set memory limits when running containers to prevent individual containers from consuming excessive RAM.

docker run -d --name webapp \
  --memory="512m" \
  --memory-swap="1g" \
  --oom-kill-disable=false \
  nginx:latest

This container gets 512MB RAM, 1GB total memory including swap, and will be killed if it exceeds limits rather than causing system OOM.

Configure CPU limits for containers

Control CPU usage with CPU shares, quotas, and core affinity to prevent CPU monopolization.

docker run -d --name database \
  --cpus="1.5" \
  --cpu-shares=1024 \
  --cpuset-cpus="0-1" \
  postgres:15

This limits the container to 1.5 CPU cores maximum, gives it normal priority (1024 shares), and restricts it to CPU cores 0 and 1.

Set I/O limits for containers

Control disk I/O to prevent containers from saturating storage bandwidth and affecting other workloads.

docker run -d --name logserver \
  --device-read-bps /dev/sda:50mb \
  --device-write-bps /dev/sda:30mb \
  --device-read-iops /dev/sda:1000 \
  --device-write-iops /dev/sda:800 \
  elasticsearch:8.11.0

This container can read max 50MB/s, write max 30MB/s, with IOPS limits of 1000 read and 800 write operations per second.

Create Docker Compose with resource limits

Define resource constraints in Docker Compose files for consistent multi-container deployments.

version: '3.8'
services:
  web:
    image: nginx:latest
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
        reservations:
          memory: 256M
          cpus: '0.5'
    
  db:
    image: postgres:15
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '2.0'
        reservations:
          memory: 1G
          cpus: '1.0'
    environment:
      POSTGRES_PASSWORD: secure_password_123

Configure systemd container service limits

For containers managed by systemd services, add resource limits to the service unit files.

sudo nano /etc/systemd/system/my-container.service
[Unit]
Description=Production Web Container
After=docker.service
Requires=docker.service

[Service]
Type=simple
ExecStart=/usr/bin/docker run --rm \
  --name production-web \
  --memory=1g \
  --cpus=1.5 \
  nginx:latest
ExecStop=/usr/bin/docker stop production-web
Restart=always
RestartSec=10

Systemd resource limits

MemoryAccounting=true MemoryLimit=1.5G CPUAccounting=true CPUQuota=200% TasksAccounting=true TasksMax=512 [Install] WantedBy=multi-user.target

Enable and start the container service

Activate the systemd service with dual-layer resource protection from both systemd and Docker.

sudo systemctl daemon-reload
sudo systemctl enable my-container.service
sudo systemctl start my-container.service
sudo systemctl status my-container.service

Monitor resource usage

Use these commands to monitor how containers use system resources and verify limits are working.

# Monitor Docker container resource usage
docker stats

Check systemd service resource usage

systemctl status docker --no-pager -l

View cgroup resource accounting

sudo systemd-cgtop

Check specific container cgroup usage

sudo cat /sys/fs/cgroup/system.slice/docker-$(docker inspect --format='{{.Id}}' webapp).scope/memory.current

Advanced cgroups v2 configuration

For fine-grained control, configure cgroups v2 parameters directly through systemd slice units.

Create custom container slice

Define a systemd slice with specific resource allocations for container workloads.

sudo nano /etc/systemd/system/container-workloads.slice
[Unit]
Description=Container Workloads Slice
Before=slices.target

[Slice]
MemoryAccounting=true
MemoryLimit=6G
MemoryHigh=5G
CPUAccounting=true
CPUWeight=200
CPUQuota=300%
IOAccounting=true
IOWeight=200

Assign containers to the custom slice

Run containers within the custom slice for coordinated resource management across multiple containers.

sudo systemctl start container-workloads.slice

docker run -d --name web1 \
  --cgroup-parent=/system.slice/container-workloads.slice \
  --memory=1g \
  --cpus=1.0 \
  nginx:latest

Production optimization strategies

These configurations help optimize resource utilization for production workloads while maintaining system stability.

Configure memory pressure handling

Set up memory pressure notifications to handle resource constraints gracefully before hitting hard limits.

docker run -d --name pressure-aware \
  --memory=1g \
  --memory-reservation=768m \
  --kernel-memory=100m \
  --oom-score-adj=500 \
  nginx:latest

Memory reservation creates a soft limit that triggers reclaim before the hard limit. Higher OOM score makes this container more likely to be killed in extreme memory pressure.

Implement CPU bandwidth control

Use CPU period and quota for precise CPU bandwidth allocation in multi-tenant environments.

docker run -d --name cpu-controlled \
  --cpu-period=100000 \
  --cpu-quota=150000 \
  --cpu-shares=512 \
  nginx:latest

This allows 1.5 CPU cores (150000/100000) with lower priority (512 shares vs default 1024) for background workloads.

Configure resource monitoring alerts

Set up monitoring to alert when containers approach resource limits, enabling proactive capacity management.

This approach works well with monitoring systems covered in Prometheus cgroup metrics monitoring for comprehensive resource oversight.

Verify your setup

# Check Docker service limits
sudo systemctl show docker.service | grep -E '(Memory|CPU|Tasks)'

Verify container resource usage

docker stats --no-stream

Check cgroups v2 is active

ls -la /sys/fs/cgroup/system.slice/ | grep docker

Monitor resource pressure

sudo systemd-cgtop --delay=2

Common issues

SymptomCauseFix
Container killed with exit code 137Memory limit exceeded (OOMKilled)Check docker logs container_name and increase memory limit or optimize application
Docker service consuming too much RAMNo systemd service limits setConfigure MemoryLimit in systemd override file
Resource limits not enforcedcgroups v1 or missing accountingEnable cgroups v2 and set MemoryAccounting=true
Container CPU throttlingCPU quota too restrictiveMonitor with docker stats and adjust --cpus parameter
I/O limits not workingWrong device specifiedUse lsblk to find correct device and update --device-*-bps settings

Next steps

Running this in production?

Want this handled for you? Setting up resource limits once is straightforward. Keeping them tuned, monitored, and adjusted as workloads change across environments is the harder part. See how we run infrastructure like this for European teams running containerized workloads at scale.

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.