Memory & Resource Limits
Pano is built to run on a tight budget — a full instance (the JVM plus the UI runtimes it spawns) targets ~1 GB of RAM, not counting the database, which runs as a separate service.
How Pano uses memory
A running instance is made of independent OS processes:
- Pano core (JVM) — the main
Pano.jarprocess. Bounded by JVM flags (-Xmx, …) given at launch. - UI runtimes (Bun) — Pano spawns one small Bun process per UI it serves:
setup-ui,panel-uiand the active theme. They are stateless SSR renderers (take data from the backend, render HTML), so they stay small. - Database — a separate application (MariaDB/MySQL). Limit it on its own.
Total RAM ≈ JVM (
-Xmx+ metaspace + direct + overhead) + Σ each UI runtime. They are separate processes: JVM flags do not limit the Bun UIs, and the UI cap does not limit the JVM. Size both.
Limit the Pano core (JVM)
Heap size is fixed at startup and cannot shrink while running, so cap it (and the off-heap regions) when you launch Pano.
JAVA_TOOL_OPTIONS (recommended)
The JVM reads this environment variable automatically — your start command stays unchanged:
export JAVA_TOOL_OPTIONS="-Xms128m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError"
java -jar Pano.jarFlags on the command line
java -Xms128m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError -jar Pano.jar| Flag | Purpose |
|---|---|
-Xmx512m | Max heap — the biggest lever. |
-XX:MaxMetaspaceSize=256m | Caps class metadata. |
-XX:MaxDirectMemorySize=64m | Caps Netty/Vert.x off-heap buffers. |
-XX:+UseSerialGC | Lowest-overhead GC for small heaps. |
-XX:+ExitOnOutOfMemoryError | Exit (so the supervisor restarts) instead of hanging on OOM. |
Docker
ENV JAVA_OPTS="-Xms128m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar Pano.jar"]Cap the whole container so the JVM and the Bun UIs share one hard ceiling:
docker run --memory=1g --memory-swap=1g your-pano-imagesystemd (Linux host)
[Service]
Environment="JAVA_TOOL_OPTIONS=-Xms128m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError"
ExecStart=/usr/bin/java -jar /opt/pano/Pano.jar
# Hard-cap the whole service (JVM + spawned UIs):
MemoryMax=1G
MemorySwapMax=0Limit the UI runtimes (setup-ui, panel-ui, theme)
There is no portable per-process hard cap for the spawned Bun UIs (cgroups are Linux-only, Windows Job Objects are Windows-only, macOS has neither), so Pano caps them itself — the same way on every platform it runs (Linux, macOS, Windows, Android):
- It launches Bun in low-memory mode (
--smol). - A built-in watchdog periodically reads each UI process's resident memory and restarts any that exceeds the limit. The restart reuses the same internal port, so routing is untouched; the UIs are stateless, so it's safe.
Set the per-UI ceiling in your config (see Server Configuration):
server {
ui-max-memory-mb = 200 # max MB per UI runtime; 0 disables the limit
}- Default 200 MB per UI.
- With
--smolthe UIs normally sit well below this — the cap is a safety net against a leak or runaway. 0disables enforcement.
A worked ~1 GB budget
With the database elsewhere, give the JVM the larger share and leave room for the one or two UIs usually running (panel-ui + the active theme):
# Pano core (JVM)
export JAVA_TOOL_OPTIONS="-Xms128m -Xmx448m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError"
java -jar Pano.jar// config — keep each UI small
server {
ui-max-memory-mb = 200
}Rough math: JVM ≈ 448 MB heap + ~250 MB overhead ≈ ~700 MB, plus 2 × ~120 MB UIs ≈ ~240 MB → ~940 MB, comfortably under 1 GB. Tune -Xmx and ui-max-memory-mb for your traffic.
The database is separate — give it its own limit (e.g. another container with its own
--memory).