diff --git a/docs/ops/2026-10-12-cloud-hosting-requirements.md b/docs/ops/2026-10-12-cloud-hosting-requirements.md new file mode 100644 index 0000000..fead7b4 --- /dev/null +++ b/docs/ops/2026-10-12-cloud-hosting-requirements.md @@ -0,0 +1,108 @@ +# Cloud hosting requirements — occupymedia.cc stack + +Date: 2026-10-12 +Basis: measured on the production home deployment (CT 150 app, CT 152 chat, +CT 320 files) — see the table for real numbers, not estimates. + +## What runs where + +Everything except object storage and the CF edge is self-hostable on ONE VPS: + +- Hub app: api, scheduler, web (Next.js), intake site, Element Web client +- Data: Postgres, Redis, Temporal, chat homeserver (tuwunel/RocksDB), + files server (oCIS S3NG + optional Collabora) +- Edge: cloudflared tunnel, nginx edge, certbot, failover watchdogs +- Media worker (transcoding): can stay OFF the box (distributed worker model — + e.g. the planned Windows/NVENC node joins over tailscale) + +Externalized by design (never self-host): **R2** object storage (all media +payloads — files, chat media, video), **Cloudflare** (DNS, ingress, failover), +Telegram/Matrix federation peers. + +## Measured footprint (production, today) + +| Component | RAM used | Disk used | Notes | +|---|---|---|---| +| App CT (everything above) | 1.0 GB / 16 GB | 147 GB* | *images + build artifacts + snapshots — real data is small | +| Chat CT (tuwunel + RocksDB) | 145 MB / 4 GB | 7.8 GB | grows slowly: new media → R2 | +| Files CT (oCIS + Collabora) | 1.4 GB / 8 GB | 13 GB | metadata only; payloads in R2 | +| R2 (object storage) | — | 385 GB on account** | **occupymedia usage ≈ 0 today; the 385 GB is another project sharing the account | + +Real concurrent RAM: **~2.6 GB total**. Media payloads are externalized to R2, +so disk does NOT grow with video/uploads. + +## Sizing + +| Tier | Specs | What it covers | Ref. price (Hetzner-class) | +|---|---|---|---| +| Minimum | 4 vCPU / 8 GB RAM / 160 GB SSD | full stack at current usage | ~€15/mo | +| Comfortable | 8 vCPU / 16 GB RAM / 240 GB SSD | + live-event spikes, on-box transcoding, Collabora sessions | ~€30/mo | + +- **CPU**: 4 vCPU is enough if video transcoding stays distributed (render + workers join over tailscale — the design). On-box transcoding wants 8. +- **RAM**: 8 GB floor (Temporal + oCIS + Postgres headroom); 16 GB makes live + events boring. +- **Disk**: does not grow with media (R2 externalized). 80–160 GB lasts years. +- **Bandwidth**: near-zero egress (R2/Cloudflare deliver media; R2 has zero + egress fees). +- **Object storage (R2)**: pay-per-GB (~$0.015/GB/mo) + small ops fees. Current + occupymedia usage ≈ 0; expect low single-digit $/mo at collective scale. + +## Single-box placement + +One VPS runs the consolidated stack (all three home CTs on one host): + +- Docker compose projects: occ-app, occ-chat, occ-files, occ-edge, occ-element, + occ-intake — the same compose files in `infra/occupymedia/`. +- Ingress: Cloudflare tunnel (remotely managed — config lives in the CF + dashboard, re-point at the new box) + the edge nginx + watchdogs (the ipfs3 + relay failover works from any location; from a cloud box it is redundant but + harmless). +- Tailscale optional on a cloud box (its address is already public). + +## Migration runbook (home → cloud) + +Ordered; each step reversible: + +1. **Prereq**: fresh VPS (Ubuntu 24.04), Docker + compose plugin, tailscale + (optional), DNS ready (CF API token). +2. **Data export** (from home, per store): + - Postgres: `pg_dump` (owner format) → transfer. + - Chat: stop tuwunel → tar `occ-chat_conduwuit_data` volume (final snapshot + exists; make a fresh one) → transfer. Keep `tuwunel.toml` (has R2 creds). + - Files: stop oCIS → tar `ocis-config` + `ocis-metadata` volumes → transfer. + (Blobs are in R2 — they do not move.) + - Hub files folders: they are oCIS resources (in R2) — nothing extra. +3. **R2**: unchanged — same account, same buckets, same keys. If the VPS region + differs, no action (R2 is anycast). +4. **Restore on the VPS**: start postgres → restore dump → restore chat/files + volumes from tars → start stacks in order (datastores → api/scheduler/web → + chat → files → edge) → apply any pending migrations with the api CLI. +5. **Ingress cutover**: install cloudflared with the same tunnel token (the + tunnel id moves with the token; ingress config is remote) → connections + register from the new box. Re-point tunnel ingress service URLs from + `192.168.13.x`/`100.x` addresses to the VPS's local container names/ports. +6. **Edge + watchdogs**: copy the edge dir; the ipfs3 relay and ingress + watchdog keep working (the watchdog flips DNS to the relay if the new box's + CF path is ever sick). +7. **Verify** (the standard checklist): api/web/chat/files/element health, + login, collective creation mints rooms, archive E2E (upload → R2 → bot → + files), restore tooling round-trip. +8. **Rollback**: home stack stays intact until step 7 passes; DNS flips back in + one API call. + +## Cost picture + +| Item | Monthly | +|---|---| +| VPS (comfortable tier) | ~€30 | +| R2 storage (grows with media) | ~$1–5 at collective scale, zero egress | +| Cloudflare free tier | $0 | +| **Total** | **~€30–35/mo all-in** | + +## What was measured vs assumed + +Measured: RAM/disk per CT (table above), R2 bucket inventory + usage, docker +image sizes, component list. Assumed: audience scale stays collective-sized +(tens–hundreds of users); video delivery rides R2/CF (no self-hosted CDN +egress); transcoding off-box. Re-measure after the first large live event.