Skip to content

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:

  1. Pano core (JVM) — the main Pano.jar process. Bounded by JVM flags (-Xmx, …) given at launch.
  2. UI runtimes (Bun) — Pano spawns one small Bun process per UI it serves: setup-ui, panel-ui and the active theme. They are stateless SSR renderers (take data from the backend, render HTML), so they stay small.
  3. 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.

The JVM reads this environment variable automatically — your start command stays unchanged:

bash
export JAVA_TOOL_OPTIONS="-Xms128m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError"
java -jar Pano.jar

Flags on the command line ​

bash
java -Xms128m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError -jar Pano.jar
FlagPurpose
-Xmx512mMax heap — the biggest lever.
-XX:MaxMetaspaceSize=256mCaps class metadata.
-XX:MaxDirectMemorySize=64mCaps Netty/Vert.x off-heap buffers.
-XX:+UseSerialGCLowest-overhead GC for small heaps.
-XX:+ExitOnOutOfMemoryErrorExit (so the supervisor restarts) instead of hanging on OOM.

Docker ​

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

bash
docker run --memory=1g --memory-swap=1g your-pano-image

systemd (Linux host) ​

ini
[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=0

Limit 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):

jsonc
server {
  ui-max-memory-mb = 200   # max MB per UI runtime; 0 disables the limit
}
  • Default 200 MB per UI.
  • With --smol the UIs normally sit well below this — the cap is a safety net against a leak or runaway.
  • 0 disables 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):

bash
# Pano core (JVM)
export JAVA_TOOL_OPTIONS="-Xms128m -Xmx448m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=64m -XX:+UseSerialGC -XX:+ExitOnOutOfMemoryError"
java -jar Pano.jar
jsonc
// 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).