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
137from 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 withdmesg -T | grep -i oomorjournalctl -k --since "-5 minutes" | grep -i oom—docker inspecton the container itself won’t showOOMKilled: truefor 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 againstget_host_resources()(reads/proc/meminfo,os.cpu_count(), andos.statvfs()onFILESTORE_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) + 1formula.