Installation
System Resources & Tuning
Minimum and recommended RAM/CPU for a QuoteNode deployment, how container limits work, how to adjust them, and how to avoid out-of-memory issues.
System Resources & Tuning
QuoteNode runs as a small set of Docker containers. The Compose files ship with memory limits tuned for a 4 GB-RAM VM and no CPU cap by default, and every limit is overridable with an environment variable, so you can right-size the stack for your machine.
Minimum and recommended
| Tier | Host RAM | Host CPU | Notes |
|---|---|---|---|
| Minimum | 4 GB | 2 cores | Default profile. Comfortable for a small team (≈10 users / ≈1 000 products). |
| Recommended | 8 GB | 4 cores | Headroom for concurrent imports/exports and PDF bursts. |
| Loaded | 8–16 GB | 4–8 cores | ≈100 users / ≈20 000 products with parallel bulk operations. |
The stack is idle-light: at rest the whole stack uses about 1.5 GiB of RAM regardless of how much data is in the database (there is no in-JVM entity cache — data lives in PostgreSQL). RAM scales with concurrency of bulk operations (import/export/PDF), not with the number of users or records.
How container limits work
mem_limit is a hard ceiling, not a reservation. A container uses only what it actually touches, up to the limit; a container that exceeds its limit is OOM-killed. A higher limit does not cost memory — it just raises the safety boundary.
The default profile sums to about 3 520 MiB of ceilings, which fits a 4 GB host with ~575 MiB left for the operating system. Because real idle usage is far lower (~1.5 GiB), the ceilings are headroom for load spikes, not expected consumption.
cpus is a ceiling too, but CPU is free when idle, so no CPU ceiling is set by default: every service may use all host cores and idle CPU costs nothing. If you set a *_CPUS ceiling, keep it at or below the host’s core count — Docker refuses to start a container whose cpus exceeds the available cores.
Default per-service limits (4 GB profile)
| Service | Memory | CPU | Role |
|---|---|---|---|
| backend | 1536m | all cores | API / web server (JVM heap auto-sizes to 70% of this) |
| postgres | 512m | all cores | Database |
| backup-worker | 832m | all cores | Scheduled backups (pg_dump) |
| gotenberg | 448m | all cores | Chromium PDF rendering |
| frontend | 96m | all cores | Caddy serving the static build |
| caddy | 96m | all cores | TLS / reverse-proxy ingress — raise CADDY_MEM_LIMIT for heavy TLS traffic |
Adjusting RAM
Every limit is an optional environment variable. Set it where your installation keeps its settings:
- Release package (server, LAN, laptop or a NAS folder): the
.envfile next todocker-compose.yml; then rundocker compose up -d, which recreates the changed containers. - Portainer: the stack’s environment variables (
stack.env); then update the stack. - Coolify: its Compose file sets no per-service limits, so these variables have no effect there.
# Example: an 8 GB machine with more concurrent load
BACKEND_MEM_LIMIT=3g
POSTGRES_MEM_LIMIT=1g
GOTENBERG_MEM_LIMIT=768m
The backend’s Java heap auto-scales to 70% of BACKEND_MEM_LIMIT (via MaxRAMPercentage), so raising the limit raises the heap automatically — no JVM flags to edit. The same applies proportionally to the backup-worker (which uses 45%).
Available variables: BACKEND_MEM_LIMIT, BACKEND_CPUS, POSTGRES_MEM_LIMIT, POSTGRES_CPUS, WORKER_MEM_LIMIT, WORKER_CPUS, GOTENBERG_MEM_LIMIT, GOTENBERG_CPUS, FRONTEND_MEM_LIMIT, FRONTEND_CPUS, CADDY_MEM_LIMIT, CADDY_CPUS.
CPU
Leave CPU uncapped unless another workload on the host needs protecting. The only place CPU matters in practice is cold start: the JVM-based backend and worker boot a large application context, which is CPU-bound. On a single core a cold start can take minutes; with 2+ cores it is well under a minute. The worker’s health-check grace period already accounts for slow starts, so a constrained machine recovers on its own without a restart loop.
Avoiding out-of-memory issues
- Do not shrink
WORKER_MEM_LIMITbelow ~768m. A backup run (pg_dump+tar+gzip+ the JVM) peaks at about 607 MiB; with less headroom it can be killed mid-backup. - On a 2 GB VM, the default profile is too large — lower the limits (e.g.
BACKEND_MEM_LIMIT=768m,POSTGRES_MEM_LIMIT=384m) so the sum stays below physical RAM. The backend heap follows the limit automatically. - Watch the host, not just containers. If the host runs other workloads, count their memory too — Docker only enforces per-container ceilings, not total host usage.
- A container repeatedly restarting with exit code 137 indicates an OOM kill; raise that service’s
*_MEM_LIMIT.