Resource Limits & DoS Prevention

If you don’t limit your containers, a single bug (memory leak) or an attacker (fork bomb) can crash your entire server.

Control Groups (Cgroups) are the Linux kernel feature that limits, accounts for, and isolates the resource usage (CPU, memory, disk I/O, network) of a collection of processes.

1. The Threat: Resource Exhaustion

  1. Memory Leaks: A container consumes all 32GB of RAM. The OS starts killing other processes (SSH, Database) to survive.
  2. CPU Starvation: A crypto-mining malware spins 100% CPU, making the API unresponsive.
  3. Fork Bomb: A script creates infinite processes, filling the process table.

2. Interactive: The OOM Killer Game

When RAM is full, the Linux Kernel’s Out of Memory (OOM) Killer wakes up. It calculates a score for every process and kills the “worst” offender to save the system.

RAM
DB
App
Malware
System Normal.

3. Setting Limits

1. Docker CLI

Hard limits enforce a strict ceiling. If exceeded, the container is throttled (CPU) or killed (Memory).

# Limit to 0.5 CPU cores and 512MB RAM
docker run -d \
  --cpus="0.5" \
  --memory="512m" \
  --memory-swap="512m" \
  nginx

[!WARNING] Disable Swap: Always set --memory-swap equal to --memory to disable swap. Swapping to disk kills performance and bypasses memory limits.

2. Docker Compose

Version 3+ uses the deploy key (originally for Swarm, now supported in Compose V2).

services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

4. Language Awareness

Old versions of Java and Go did not respect Cgroup limits. They saw the Host’s RAM (e.g., 64GB) instead of the Container’s limit (512MB), causing immediate OOM crashes.

Java (JVM)

Java 10+ is container-aware by default. For Java 8 (u191+), use:

java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -jar app.jar

Go (Golang)

Go 1.19+ automatically sets GOMAXPROCS to match the CPU quota if automaxprocs is used.

import _ "go.uber.org/automaxprocs"

func main() {
  // Automatically sets GOMAXPROCS to match Linux container CPU quota
}

5. Best Practices

  1. Always set limits: Never run a container without a memory limit.
  2. Leave headroom: If your app needs 200MB, set the limit to 256MB.
  3. Monitor OOM Kills: Check docker inspect to see if a container was OOM killed.
    docker inspect -f '{{.State.OOMKilled}}' my-container