--- title: "Docker Compose CPU Limits Break Per Environment" aliases: [docker-cpu-limits, compose-cpu-env, cpus-range-error, docker-memory-reservation-limit] tags: [docker, docker-compose, deployment, devops, gotcha] sources: - "daily/2026-04-29.md" - "daily/2026-04-30.md" created: 2026-04-29 updated: 2026-04-30 --- # Docker Compose CPU Limits Break Per Environment Setting `cpus: '4.0'` in a prod `docker-compose.yml` and then running `docker compose up` on a server with only 2 CPUs causes a hard error. The fix is a per-environment compose override file that matches the target server's actual CPU count. ## Key Points - Error message: `"range of CPUs is from 0.01 to 2.00"` — appears at `docker compose up`, NOT at `docker compose build` - The build succeeds silently; the failure only surfaces at container startup - prod compose is the canonical config; environment-specific files override resource limits only - Naming convention: `docker-compose.{env}.yml` (e.g. `docker-compose.optical-dev.yml`) ## Details The error occurs because Docker enforces CPU limits against the host's actual CPU count at container start time, not at image build time. A prod file with `cpus: '4.0'` is valid on a 4-core prod server but fails immediately on a 2-core staging server. **Prod compose (canonical):** ```yaml services: ffmpeg-worker: deploy: resources: limits: cpus: '4.0' # valid on 4-core prod memory: 8G ``` **optical-dev override:** ```yaml # docker-compose.optical-dev.yml services: ffmpeg-worker: deploy: replicas: 0 # also disable heavy workers on this env resources: limits: cpus: '1.0' # optical-dev has 2 CPUs — keep 1 for other services memory: 2G ``` **Deploy command using override:** ```bash docker compose -f docker-compose.yml -f docker-compose.optical-dev.yml up -d ``` ## Pattern | File | Role | |------|------| | `docker-compose.yml` | Canonical config — prod values, all services defined | | `docker-compose.prod.yml` | Prod-specific overrides (rarely needed if base IS prod) | | `docker-compose.optical-dev.yml` | optical-dev overrides — reduced CPU/RAM, some services disabled | | `docker-compose.override.yml` | Local dev — picked up automatically by Docker Compose | ## Memory Reservation > Limit Error Across Override Files A related Docker Compose gotcha: Docker enforces `memory limit >= memory reservation` strictly at container start time. This breaks silently when override files set smaller limits without redefining reservations. ### Error Message ``` Error response from daemon: Minimum memory limit can not be less than memory reservation limit ``` ### The Conflict ```yaml # docker-compose.prod.yml — sets large reservations services: mongodb: deploy: resources: reservations: memory: 2G # docker-compose.optical-dev.yml — reduces limits but forgets reservations services: mongodb: deploy: resources: limits: memory: 1G # ERROR: 1G limit < 2G reservation inherited from prod # reservations: not redefined — Docker inherits 2G from prod file ``` ### Fix: Redefine Reservations in Every Override That Reduces Limits ```yaml # docker-compose.optical-dev.yml services: mongodb: deploy: resources: limits: memory: 1G reservations: memory: 512M # must be <= limit ``` The rule: **every override layer that changes memory limits must also set reservations ≤ new limit**. ## Related Concepts - [[wiki/architecture/cloud-run-jobs-celery|cloud-run-jobs-celery]] — why optical-dev uses this pattern for heavy workers - [[wiki/architecture/optical-dev-server-deploy|optical-dev-server-deploy]] — optical-dev server constraints and deployment pattern ## Sources - [[daily/2026-04-29.md]] — session 20:29, `cpus: '4.0'` breaking on 2-CPU optical-dev server - [[daily/2026-04-30.md]] — session 12:11, memory reservation > limit error in override files