obsidian/wiki/concepts/docker-compose-cpu-limits-env.md
2026-04-30 21:23:56 +01:00

123 lines
4 KiB
Markdown

---
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