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