Resource limits

Every instance carries cpu (cores), memory (MB) and storage (GB) fields on saas.instance, sourced from the customer’s plan at creation time. How those numbers actually get enforced differs by backend.

Kubernetes

Set directly as pod resource requests and limits (identical values — no burstable headroom):

resources.requests = {"cpu": f"{cpu}", "memory": f"{memory_mb}Mi"}
resources.limits   = {"cpu": f"{cpu}", "memory": f"{memory_mb}Mi"}

Storage is a PVC sized in whole GiB (resources.requests.storage = f"{int(storage_gb)}Gi") on the storage class in K8S_FILESTORE_STORAGE_CLASS (default gp3). The custom-addons PVC is separate from the filestore PVC and always 2Gi regardless of plan — addon code doesn’t scale with the customer’s storage tier.

Docker

Enforced at container creation with explicit mem_limit=f"{memory_mb}m" and nano_cpus=int(cpu * 1_000_000_000) — both must be set explicitly per container, since Docker doesn’t inherit limits from anywhere else.

Why one-off containers matter for memory

During initialization, the filestore check and odoo -i base each run in their own throwaway container (_docker_run_oneoff) rather than exec’ing into the persistent server. Running the init process inside the already-running server would mean two Odoo processes competing for the same mem_limit at once — which is exactly what was causing OOM kills (exit code 137) on tighter plans. The persistent container is only created once -i base has already succeeded.

Diagnosing resource exhaustion

  • Exit code 137 from any Docker exec or one-off run is a strong OOM signal — the kernel killed the process outright rather than Odoo exiting cleanly. Confirm on the Docker host with dmesg -T | grep -i oom or journalctl -k --since "-5 minutes" | grep -i oomdocker inspect on the container itself won’t show OOMKilled: true for this case, since it’s the exec’d child that gets killed, not the container’s PID1.

  • Host-level pre-checks — before Docker/Kubernetes provisioning even starts, check_host_resources() compares the requested CPU/memory/storage against get_host_resources() (reads /proc/meminfo, os.cpu_count(), and os.statvfs() on FILESTORE_BASE_PATH), reserving 256MB of memory and 2GB of disk as headroom. Provisioning fails fast with a clear “Insufficient server memory/CPU/storage” message rather than starting a container that’s doomed to fail.

  • Worker sizing for Odoo’s own worker processes follows the standard (cores × 2) + 1 formula.