b4dcf4b373
gates / gates (push) Successful in 6m35s
plugswitch.py refuses 'off' unless 20 s of readings show the N100 is already off (standby + 1 W, no ping); 7 tests, each check red-proofed. By hand: guest shutdown 12 s, host off, 0.0 W; the hub stayed silent and 'ONLINE' for 18 min; plug off/on did not start the N100, its power button did (dashboard in 42 s). Wall figures into SIZING.md 4 and sizing.json power (8.5 / 20.7 / 0.0 W).
186 lines
13 KiB
Markdown
186 lines
13 KiB
Markdown
# What a Felhom box needs — sizing, 2026-10-13
|
||
|
||
Measured on the N100 demo box (`demo-felhom`: Intel N100, 4 cores, 16 GB). Method, sources and limits: `METHOD.md`.
|
||
Data file for the future estimator: `sizing.json` (rebuilt by `tools/build_sizing.py`). Memory is MB (MiB),
|
||
docker-style `used`. CPU is % of the whole N100 (4 cores = 100 %).
|
||
|
||
## 1. The floor — the box with no apps
|
||
|
||
| Part | Memory | Disk |
|
||
|---|---|---|
|
||
| Whole host (Proxmox + agent + guest + platform), `MemTotal − MemAvailable` | **2,494 MB** (peak 2,793) | host root 31 GB used of 94 GB, of which 19 GB = 3 local guest dumps |
|
||
| — of which inside the guest: platform containers + Docker daemons | ≈ 470 MB | guest system 1.1 GB, Docker volume 1.6 GB (platform images 1.55 GB) |
|
||
| traefik (proxy) | 113 MB (anon 15) | — |
|
||
| filebrowser (file browser) | 75 MB (anon 18) | — |
|
||
| cloudflared (tunnel) | 38 MB (anon 16) | — |
|
||
| felhom-controller | 29 MB (anon 28) | — |
|
||
| dockerd + containerd + shims | ≈ 220 MB RSS | — |
|
||
| CPU at rest, platform only | 0.4 % of the N100 | — |
|
||
| Power at rest (CPU package, approximate) | 3.9 W | — |
|
||
|
||
Samba is not in the floor: it runs only when sharing is switched on. Evidence: `evidence/floor/`.
|
||
|
||
## 2. Each app
|
||
|
||
<!-- apps-table: generated by tools/render_table.py -->
|
||
| App | Typical MB | Peak MB | Catalog request / limit | CPU class | CPU rest % | Busiest minute % | Source |
|
||
|---|---|---|---|---|---|---|---|
|
||
| Immich (`immich`) | 1,356 | 3,061 | 2,048 / 4,480 | heavy-in-bursts | 0.74 | 74.2 | onboarding ! |
|
||
| Grimmory (`grimmory`) | 346 | 1,095 | 600 / 1,408 | medium | 0.83 | 22.0 | onboarding ! |
|
||
| Karakeep (`karakeep`) | 361 | 1,050 | 700 / 2,816 | light | 0.6 | 2.9 | onboarding ! |
|
||
| Cal.com (`calcom`) | 800 | 950 | 200 / 1,792 | light | 0.14 | 1.8 | onboarding |
|
||
| Dawarich (`dawarich`) | 390 | 890 | 500 / 2,688 | light | 0.48 | 14.4 | onboarding |
|
||
| Tandoor Recipes (`tandoor`) | 868 | 868 | 512 / 1,280 | medium | 0.16 | 20.0 | onboarding |
|
||
| RomM (`romm`) | 528 | 769 | 300 / 1,280 | medium | 0.71 | 24.2 | telemetry |
|
||
| SparkyFitness (`sparkyfitness`) | 305 | 664 | 400 / 1,792 | light | 0.23 | 0.4 | onboarding ! |
|
||
| Paperless-ngx (`paperless-ngx`) | 568 | 659 | 500 / 1,664 | medium | 0.37 | 31.3 | telemetry |
|
||
| n8n (`n8n`) | 321 | 550 | 512 / 1,536 | light | 0.1 | 0.5 | onboarding ! |
|
||
| Nextcloud (`nextcloud`) | 314 | 525 | 256 / 1,664 | light | 0.4 | 12.8 | onboarding ! |
|
||
| Komga (`komga`) | 442 | 496 | 200 / 768 | light | 0.15 | 0.5 | onboarding |
|
||
| Docmost (`docmost`) | 415 | 495 | 200 / 896 | light | 0.38 | 0.8 | telemetry |
|
||
| OnlyOffice (`onlyoffice`) | 364 | 488 | 512 / 2,048 | medium | 0.04 | 31.8 | measured |
|
||
| Rallly (`rallly`) | 360 | 479 | 384 / 1,024 | light | 0.19 | 2.9 | onboarding |
|
||
| AdventureLog (`adventurelog`) | 361 | 464 | 100 / 896 | medium | 0.45 | 19.3 | telemetry |
|
||
| Grafana (`grafana`) | 220 | 435 | 100 / 512 | light | 0.31 | 1.0 | onboarding ! |
|
||
| Mealie (`mealie`) | 273 | 431 | 200 / 1,000 | light | 0.15 | 1.4 | onboarding ! |
|
||
| Calibre-Web Automated (`calibre-web`) | 191 | 413 | 200 / 768 | medium | 0.04 | 26.0 | telemetry ! |
|
||
| Papra (`papra`) | 266 | 405 | 256 / 768 | light | 0.07 | 0.2 | onboarding |
|
||
| Outline (`outline`) | 282 | 393 | 200 / 1,152 | light | 0.39 | 1.4 | onboarding |
|
||
| Jellyfin (`jellyfin`) | 191 | 373 | 512 / 2,048 | heavy-in-bursts | 0.06 | 51.3 | measured |
|
||
| Gramps Web (`gramps-web`) | 326 | 342 | 384 / 1,024 | light | 0.04 | 0.0 | onboarding |
|
||
| MeTube (`metube`) | 62 | 329 | 128 / 768 | light | 0.03 | 0.1 | onboarding ! |
|
||
| Home Assistant (`home-assistant`) | 318 | 328 | 256 / 1,024 | light | 0.2 | 2.4 | onboarding |
|
||
| qBittorrent (`qbittorrent`) | 163 | 318 | 192 / 1,024 | light | 0.04 | 0.1 | onboarding ! |
|
||
| wger (`wger`) | 246 | 267 | 256 / 416 | — | – | – | onboarding — hidden |
|
||
| Plant-it (`plant-it`) | 50 | 256 | 50 / 256 | — | – | – | unmeasured — abandoned |
|
||
| Claper (`claper`) | 248 | 248 | 100 / 640 | light | 0.3 | 0.5 | onboarding |
|
||
| Kimai (`kimai`) | 163 | 240 | 100 / 640 | light | 0.16 | 0.8 | telemetry ! |
|
||
| Plex (`plex`) | 232 | 234 | 512 / 2,048 | light | 0.22 | 3.4 | measured |
|
||
| Uptime Kuma (`uptime-kuma`) | 103 | 227 | 50 / 256 | light | 0.2 | 0.2 | onboarding ! |
|
||
| BookStack (`bookstack`) | 159 | 225 | 150 / 768 | light | 0.18 | 0.9 | telemetry ! |
|
||
| Zipline (`zipline`) | 196 | 217 | 100 / 768 | light | 0.2 | 0.3 | onboarding |
|
||
| Wishlist (`wishlist`) | 115 | 184 | 120 / 512 | light | 0.07 | 0.1 | onboarding ! |
|
||
| Jellyseerr (`seerr`) | 166 | 168 | 100 / 384 | light | 0.04 | 0.1 | measured |
|
||
| ActualBudget (`actualbudget`) | 44 | 167 | 50 / 256 | light | 0.06 | 0.1 | measured ! |
|
||
| Ghost (`ghost`) | 149 | 149 | 150 / 512 | light | 0.08 | 0.2 | onboarding |
|
||
| Wanderer (`wanderer`) | 143 | 147 | 350 / 1,024 | light | 0.2 | 0.3 | measured |
|
||
| Emby (`emby`) | 142 | 146 | 512 / 2,048 | light | 0.18 | 1.9 | onboarding ! |
|
||
| Gitea (`gitea`) | 74 | 136 | 100 / 512 | light | 0.04 | 0.1 | onboarding ! |
|
||
| Homebox (`homebox`) | 17 | 118 | 50 / 256 | light | 0.06 | 0.1 | measured ! |
|
||
| Termix (`termix`) | 102 | 117 | 30 / 128 | light | 0.04 | 0.1 | measured |
|
||
| Grocy (`grocy`) | 30 | 114 | 60 / 384 | light | 0.04 | 0.3 | onboarding ! |
|
||
| LubeLogger (`lubelogger`) | 53 | 113 | 80 / 512 | light | 0.08 | 0.3 | onboarding ! |
|
||
| Vikunja (`vikunja`) | 20 | 104 | 50 / 256 | light | 0.0 | 0.0 | measured ! |
|
||
| Radarr (`radarr`) | 96 | 100 | 150 / 512 | light | 0.09 | 0.3 | measured |
|
||
| Vaultwarden (`vaultwarden`) | 44 | 99 | 50 / 256 | light | 0.03 | 0.0 | onboarding ! |
|
||
| Crafty Controller (`crafty-controller`) | 78 | 84 | 256 / 2,048 | light | 0.09 | 0.2 | onboarding ! |
|
||
| Homepage (`homepage`) | 82 | 83 | 50 / 256 | light | 0.03 | 0.0 | measured |
|
||
| Gokapi (`gokapi`) | 7 | 77 | 30 / 128 | light | 0.03 | 0.0 | measured ! |
|
||
| Sonarr (`sonarr`) | 77 | 77 | 150 / 512 | light | 0.07 | 0.2 | onboarding |
|
||
| Recipe Importer (`recipe-importer`) | 63 | 72 | 30 / 128 | light | 0.09 | 0.1 | measured |
|
||
| OpenGist (`opengist`) | 27 | 68 | 30 / 128 | light | 0.03 | 0.8 | telemetry |
|
||
| SABnzbd (`sabnzbd`) | 68 | 68 | 192 / 1,024 | light | 0.04 | 0.1 | onboarding |
|
||
| Code-Server (`code-server`) | 59 | 59 | 200 / 1,024 | light | 0.03 | 0.0 | measured |
|
||
| PrivateBin (`privatebin`) | 40 | 59 | 30 / 128 | light | 0.04 | 0.0 | telemetry ! |
|
||
| Audiobookshelf (`audiobookshelf`) | 48 | 54 | 100 / 512 | light | 0.04 | 0.1 | onboarding |
|
||
| Radicale (`radicale`) | 27 | 27 | 40 / 128 | light | 0.03 | 0.0 | onboarding |
|
||
| Transmission (`transmission`) | 6 | 22 | 64 / 512 | light | 0.05 | 0.1 | onboarding ! |
|
||
| Navidrome (`navidrome`) | 14 | 18 | 50 / 256 | light | 0.04 | 0.1 | onboarding |
|
||
| BentoPDF (`bentopdf`) | 5 | 10 | 100 / 384 | light | 0.03 | 0.0 | telemetry ! |
|
||
| Glance (`glance`) | 6 | 7 | 20 / 128 | light | 0.03 | 0.0 | measured |
|
||
| NZBGet (`nzbget`) | 5 | 5 | 64 / 512 | light | 0.04 | 0.1 | onboarding |
|
||
<!-- /apps-table -->
|
||
|
||
`source`: telemetry = hub fleet data ≥ 7 days · onboarding = an earlier session's measured peak · measured = this bench ·
|
||
unmeasured = catalog value. `!` = two sources' peaks differ by more than 50 %; the higher is used (all values:
|
||
`evidence/apps-table.json`). `—` = not installable today (hidden / abandoned), no bench.
|
||
|
||
**The five largest peaks:** Immich 3,061 · Grimmory 1,095 · Karakeep 1,050 · Cal.com 950 · Dawarich 890 MB.
|
||
**CPU bursts (heavy-in-bursts):** Immich — 74 % for the busiest minute, while it reads new photos; Jellyfin — 51 %,
|
||
while it converts a film (CPU only, no GPU passed through). **Medium:** Paperless-ngx 31 % (reading scans),
|
||
OnlyOffice 32 %, Calibre-Web 26 %, RomM 24 %, Grimmory 22 %, Tandoor 20 %, AdventureLog 19 % (first start).
|
||
Every other app stays under 15 % even at first start.
|
||
|
||
## 3. Catalog accuracy (§7 C — templates NOT changed; `evidence/catalog-check.json`)
|
||
|
||
**Near a memory limit (peak > 80 %) — risk: the container is stopped for using too much memory:**
|
||
|
||
| App | Finding | Source |
|
||
|---|---|---|
|
||
| docmost | container `docmost` 475 MB = 93 % of its 512 MB limit | telemetry + bench |
|
||
| romm | container `romm` 703 MB = 92 % of its 768 MB limit | telemetry + bench |
|
||
| termix | 117 MB = 91 % of its 128 MB limit | bench |
|
||
| uptime-kuma | 227 MB = 89 % of `mem_limit` 256 MB | telemetry (tester-1) |
|
||
| grafana | 435 MB = 85 % of its 512 MB limit | bench |
|
||
| immich | container `immich-server` 1,665 MB = 81 % of its 2,048 MB limit (kernel peak incl. cache 4,365 of 4,480 MB for the app) | bench, 200-photo import |
|
||
| tandoor | container `tandoor` 827 MB = 81 % of its 1,024 MB limit | bench |
|
||
|
||
No container was killed for memory during the bench (`oom_kill` = 0 in every container).
|
||
|
||
**Typical use above `mem_request` (the catalog's figure is too low), 23 apps:** calcom ×4.0 · adventurelog ×3.6 ·
|
||
termix ×3.4 · claper ×2.5 · grafana ×2.2 · komga ×2.2 · docmost ×2.1 · recipe-importer ×2.1 · uptime-kuma ×2.1 ·
|
||
zipline ×2.0 · romm ×1.8 · seerr ×1.7 · tandoor ×1.7 · homepage ×1.6 · kimai ×1.6 · mealie ×1.4 · outline ×1.4 ·
|
||
privatebin ×1.3 · home-assistant ×1.2 · nextcloud ×1.2 · bookstack ×1.1 · paperless-ngx ×1.1 · papra ×1.0.
|
||
|
||
## 4. Power
|
||
|
||
Wall figures: the whole N100 box at the socket (Tuya meter plug, tinytuya, 2 s; `audits/poweroff-2026-10-13/`,
|
||
measured the same evening). CPU figures: Intel RAPL, CPU package only (this run).
|
||
|
||
| State | Wall (plug) | CPU package (RAPL) |
|
||
|---|---|---|
|
||
| Platform at rest | **8.5 W** average, 7.2 W median (10 min) | 3.9 W (15 min) |
|
||
| Immich import, 200 photos + all jobs | **20.7 W** average, 24.0 W busiest minute, 25.2 W max (442 s) | 12.5 W average, 14.7 W busiest minute; 70 °C max (411 s) |
|
||
| Switched off (host powered off, plug on) | **0.0 W** — below the plug's 0.1 W step (18 min) | — |
|
||
|
||
Rough yearly figure at rest: 8.5 W × 8,760 h ≈ 75 kWh.
|
||
|
||
## 5. Proposed minimum machine — a proposal; the operator decides
|
||
|
||
**Worked from:** floor + the household set (Immich, Jellyfin, Nextcloud, Home Assistant, Vaultwarden, Paperless-ngx).
|
||
|
||
| | Memory |
|
||
|---|---|
|
||
| Household set, typical (sum) | 2,791 MB |
|
||
| Household set, all at peak at once (sum — they rarely coincide; deliberately pessimistic) | 5,045 MB |
|
||
| Guest platform | ≈ 470 MB |
|
||
| Guest needs at peak | ≈ 5.5 GB |
|
||
| + 50 % headroom | ≈ 8.3 GB guest |
|
||
| + the host's share (the installer keeps 4 GB for Proxmox; it used ≈ 2.0 GB here) | **≈ 12.3 GB box** |
|
||
|
||
**Headroom 50 %, because:** the bench library is small (200 photos; tester-1's real Immich already peaked at 2.9 GB);
|
||
the nightly backup runs database dumps and restic next to the apps; an update pulls new images while the old ones run;
|
||
and the page cache is what makes a photo library feel fast.
|
||
|
||
| | Minimum | Recommended |
|
||
|---|---|---|
|
||
| CPU | x86-64, 4 cores, N100 class or newer (Intel or AMD) | N100 / N150 / N305 or better; Intel Quick Sync helps Jellyfin later (not used today) |
|
||
| Memory | **8 GB** | **16 GB** |
|
||
| System disk | 256 GB SSD | 512 GB SSD (NVMe or SATA) |
|
||
| Data | a separate drive for photos and films | same |
|
||
| Out | ARM (Raspberry Pi), Synology / QNAP and other NAS appliances, routers, cloud VPS, 32-bit machines | — |
|
||
|
||
**Disk, why:** the installer gives the guest a 32 GB system volume and, from the free space, a 70 / 130 / 250 GB data
|
||
volume; the host keeps three daily guest dumps (19 GB on this box). Image sizes per app were **not measured** in this run.
|
||
|
||
**What each size runs — three verdicts, the same rule as the estimator page** (`/gepigeny`, `website/assets/sizing.js`).
|
||
*Corrected 2026-10-13:* the lines here first said an 8 GB box runs Immich „on its own” while this section sizes a box at
|
||
peak × 1.5 — which Immich alone does not meet on 8 GB ((470 + 3,061) × 1.5 ≈ 5.2 GB > 4 GB), though its peak fits
|
||
(3.4 GB). Three verdicts say both honestly. The guest is the installer's rule on MemTotal = label − 612 MiB (the
|
||
16 GB N100 reports 15,772 MiB): **8 GB → 4.0 GB, 16 GB → 11.4 GB, 32 GB → 27.4 GB** (the first version said 11.6 / 27.6).
|
||
**Fits well:** peak × 1.5 ≤ guest · **fits, but tight:** peak ≤ guest < peak × 1.5 — works, but a large photo import or
|
||
a backup at the same time can slow it down · **does not fit:** guest < peak. Peak = 470 MB platform + the apps' peaks summed.
|
||
|
||
<!-- sizing-verdicts: generated from the rule; scripts/test_build_sizing_web.py checks this table (24 GB column added 2026-10-13, estimator v2) -->
|
||
| Selection | Peak | 8 GB | 16 GB | 24 GB | 32 GB |
|
||
|---|---|---|---|---|---|
|
||
| Household set | 5.4 GB | does not fit | fits well | fits well | fits well |
|
||
| Household set without Immich | 2.4 GB | fits well | fits well | fits well | fits well |
|
||
| Immich alone | 3.4 GB | fits, but tight | fits well | fits well | fits well |
|
||
| All 62 apps | 21.3 GB | does not fit | does not fit | does not fit | fits, but tight |
|
||
<!-- /sizing-verdicts -->
|
||
|
||
- **8 GB:** the household set without Immich fits well; Immich alone fits, but tight; the whole household set does not fit.
|
||
- **16 GB:** the whole household set fits well.
|
||
- **32 GB:** the household set fits well; all 62 apps at once fit, but tight.
|