On-Prem & Cloud — My Infrastructure
A full breakdown of my infrastructure in 2026 — Proxmox cluster, Talos Kubernetes, GitOps with Flux, Oracle Cloud VPS, Tailscale mesh, and a full DR plan.

Two standalone Proxmox hosts (R730xd + Beelink), a 3-node Talos Kubernetes cluster split across them, R730xd’s SAS pool as live storage, Synology as cold backup only, Oracle Cloud VPS as the off-site anchor. One Tailscale mesh, one Git repo.
Hardware
Compute
| Device | CPU | RAM | Storage | Purpose |
|---|---|---|---|---|
Dell PowerEdge R730xd (pve) |
Xeon E5-2630 v3 (8C/16T) | 256GB DDR4 ECC | 4× 960GB SSD mirror (rpool) + 6× 600GB SAS RAIDZ2 (media) |
Proxmox — K8s control-plane 1, backup hub |
Beelink GTi 13 (px-0) |
i9-13900H (14C/20T) | 64GB DDR5 | 2× 2TB NVMe (cluster-storage) |
Proxmox — K8s control-plane 2 + 3, PDM appliance |
| Synology DS223+ | ARM RTD1619B | 2GB | 2× 2TB RAID1 | Cold backup target only |
The two OptiPlex Micro 3050s from the old 3-node cluster are retired — powered off.
Network Gear
| Device | Model | Specs | Purpose |
|---|---|---|---|
| ONT | Huawei | 1GbE | ISP Gateway |
| Firewall | XCY X44 | 8× 1GbE | pfSense Router |
| WiFi | TP-Link AX3000 | WiFi 6 | Wireless AP |
| Switch | TP-Link | 24-port | Core Switch |
An Alcatel-Lucent switch is racked but unconfigured — pending decision on replacing or supplementing the TP-Link.
Network
WAN → Orange ISP (public IP, bridge mode)LAN → 10.57.57.0/24, main homelab networkOPT1 → 10.57.97.0/24, dedicated Kubernetes node subnetVLAN → WiFi/guest, isolated from LAN except whitelisted apps (e.g. Jellyfin)pfSense
Fanless mini PC (~200€), running pfSense 3+ years: XCY X44 on AliExpress

- Tailscale Subnet Router — advertises LAN subnets to the tailnet, so every device (Synology, iDRAC, everything) is reachable without a client installed on each one. Also how the VPS reaches back into the LAN without opening any inbound port on the home public IP. Full setup guide →
- Unbound DNS — local recursive resolver, domain override for
*.k8s.merox.dev→ K8s-Gateway. - Firewall rules: WiFi→LAN blocks all except whitelisted apps; LAN→WAN allows all; WAN→Internal blocks all except explicitly exposed services.
Tailscale Mesh
One flat network everywhere — homelab rack and Oracle VPS on the same mesh, no tunnels, no firewall holes.

Virtualization
Proxmox
Two standalone hosts, no corosync cluster — pve (R730xd) and px-0 (Beelink), linked via Proxmox Datacenter Manager instead of classic HA. PDM itself runs as a VM on px-0.

Storage (per-host ZFS pools):
| Pool | Host | Type | Used | Total |
|---|---|---|---|---|
| rpool | pve | ZFS mirror (SSD) | ~140GB | 1.7TB |
| media | pve | ZFS RAIDZ2 (SAS) | ~860GB | 2.1TB |
| cluster-storage | px-0 | ZFS | ~555GB | 900GB |
R730xd NFS-exports media for the whole cluster: media/library (movies/TV/downloads, one dataset so Sonarr/Radarr/qBittorrent’s hardlink imports work), media/photos (Immich), media/isos, media/backups (Longhorn/Garage/pfSense/Immich-DB backups + the VPS’s own service backups — see Backup).
VMs and containers:
| Name | Host | Purpose | Status |
|---|---|---|---|
| kubernetes-controlplane-1 | pve | K8s node | Running |
| kubernetes-controlplane-2 | pve | K8s node | Running |
| kubernetes-controlplane-3 | pve | K8s node | Running |
| garage-r730xd (LXC) | pve | Longhorn’s S3 backup target | Running |
| datacenter-manager | px-0 | PDM appliance | Running |
| home-assistant | pve | Smart home hub | Running |
| windows11 | pve | Remote desktop | Stopped |
| winserver | px-0 | AD lab | Stopped |
| ubuntu-server | pve | Lab / testing | Stopped |
| ollama | px-0 | AI alert-triage model | Running |
Note
All three Talos control-plane VMs now live on pve — losing it takes the whole control plane at once.
Nvidia Quadro P2200 passed through to kubernetes-controlplane-1 for hardware transcoding (replaced the old Beelink/QuickSync setup). nvidia-device-plugin exposes it to K8s; Jellyfin is scheduled on that node. GPU passthrough guide →
Synology DS223+ — cold storage only
No live services — Photos, Drive, Docker all decommissioned. Asleep except once a week:
- Wakes Sunday 02 (DSM Power Schedule + WoL)
- Receives a weekly, deduplicated, versioned push from R730xd (see Backup) — local-ish recovery copy
- Shuts down shortly after
Used to also relay to Oracle via Hyper Backup — retired, R730xd pushes to Oracle directly now (see Backup).
Movies/TV/downloads: no second copy, re-downloadable, RAIDZ2 alone is enough. Photos/documents: weekly to Synology, nightly to Oracle.
Power Management
Two CyberPower UPS units — 1500VA (Proxmox hosts), 1000VA (network gear). Power failure triggers a cascading shutdown: K8s nodes drain before Proxmox hosts power off.
| Feature | Implementation | Purpose |
|---|---|---|
| pwrstat | USB to GTi13 Pro | Automated shutdown orchestration |
| SSH Scripts | Custom automation | Graceful cluster shutdown |
| Monitoring | Telegram alerts | Real-time power notifications |


Kubernetes
Talos Linux + FluxCD (GitOps). Starting point: onedr0p/cluster-template.
Repo: github.com/meroxdotdev/infrastructure
Key customizations:
| Component | Modification | Reason |
|---|---|---|
| Storage | Longhorn CSI | Simpler PV/PVC management |
| Talos Patches | Custom machine config | Longhorn requirements |
| Custom Image | factory.talos.dev | Nvidia + Intel iGPU + iSCSI support |
GitOps structure:
kubernetes/apps/├── cert-manager/ # TLS automation├── default/ # Production workloads├── flux-system/ # Flux operator + instance + n8n alerting├── kube-system/ # Cilium, CoreDNS, NFS CSI, metrics-server├── network/ # k8s-gateway, Cloudflare tunnel + DNS├── observability/ # Prometheus, Grafana, Loki└── storage/ # Longhorn configuration

Deployed apps:
| App | Purpose | Notes |
|---|---|---|
| Radarr / Sonarr | Movie/TV automation | NFS to R730xd SAS |
| Prowlarr | Indexer manager | |
| qBittorrent | Torrent client | Gluetun + SurfShark WireGuard |
| Jellyseerr | Request management | Public via Cloudflare |
| Jellyfin | Media server | NVENC/NVDEC (Quadro P2200) |
| Immich | Photos | Standalone Postgres+VectorChord, replaces Synology Photos |
| Filebrowser | SAS pool browser | WebDAV |
| Homepage | Dashboard | |
| Grafana / Prometheus / Alertmanager | Metrics + alerts | Routes to n8n — see Alerting |
| Loki + Promtail | Log aggregation | |
| pve-exporter | Proxmox host/VM/LXC metrics | Both standalone hosts, own token each |
| cert-manager | TLS automation | ACME via Let’s Encrypt |
| n8n | Workflow automation, AI alert triage | Ollama (local) + Claude Haiku (digest) |
Live status: inside.merox.dev. LoadBalancer IPs: Cilium L2 announcement, pool 10.57.57.100–120, no external LB.
Cluster Rebuild & Disaster Recovery
Fully declarative, Flux keeps state in Git — full rebuild ~35 min:
task bootstrap:appstask longhorn:restoretask longhorn:restore handles the sequence: backup target → volume restore from Garage S3 → PVs with correct claimRefs → Flux reconcile. Observability starts fresh (history isn’t backed up, dashboards come from Git).
Runbook: DR.md (tested end-to-end) · DEPLOY.md (full rebuild) · Restoring from Longhorn Backups →
Warning
Back up age.key off-site, manually — losing it loses every SOPS secret in the repo.
Cloud
Oracle Cloud Free Tier, Ampere A1 (4 vCPU / 24GB RAM / 200GB disk) — external access, backup destination, DR fallback.

Services
| Service | Purpose |
|---|---|
| Traefik | SSL termination for all VPS services |
| Cloudflare Tunnel | Outbound-only, zero inbound ports |
| Pi-hole | Tailscale split-DNS |
| Portainer | Container management |
| Authentik | SSO |
| Guacamole | Remote desktop, via Cloudflare Tunnel |
| Joplin Server | Notes sync |
| Beszel | Host/container monitoring, sub-10MB agent |
App layer: docker-compose.yml + Homepage config live in infrastructure (vps/roles/app_stack_setup/files/), deployed by Ansible. Used to be a separate repo (cloudlab-merox) — retired, it was silently overwriting other Ansible-templated services on every deploy.
Tailscale Split DNS routes *.cloud.merox.dev to Pi-hole over the tailnet — no Cloudflare round-trip for internal traffic.
Authentik — SSO for everything: proxy auth (Guacamole), OAuth2 (Portainer), K8s outpost. Full setup →
Tip
The Oracle instance also runs as a Tailscale exit node.
Disaster Recovery
Oracle can terminate Always Free instances without warning.
make dr-full provisions a Hetzner VPS on demand, deploys Ansible, brings up the app stack — ~15 min. Not a standing server, only spun up if Oracle is lost. Tunnel + Tailscale reconnect automatically with vault-stored tokens.
make dr-preflight # checks vault, age.key, Tailscale key + expiry, toolsmake dr-full # terraform apply + ansible setup + app-stack deploy (~15 min)make dr-restore # pulls R730xd backups, restores DBs + service state./scripts/dr-verify.sh --phase 1 # post-DR verificationFull walkthrough: Oracle Cloud Free Tier: Building a Full DR Plan →
Alerting
One Telegram channel, AI-triaged, replacing two Discord-only paths.
- Alertmanager + Flux → n8n → Ollama → Telegram. Critical/warning severities, tagged by layer (Proxmox, K8s, VPS, GitOps). Ollama summarizes; falls back to the raw message if it’s unreachable.
- Ollama: its own VM on
px-0, CPU-only, outside the K8s cluster. - Proxmox hosts + VMs/LXCs:
pve-exporteron both standalone hosts — host down, high CPU/memory, storage gone, VM/LXC restarts. - VPS (UPS, ZFS, fans, docker health): same n8n path. UPS and WAN/Tailscale go straight to Telegram, bypassing n8n.
- Discord: dead-man’s-switch only, two timers.

New routing immediately surfaced two silently-firing alerts (orphaned PDB, stale failed Job) — invisible under the old critical-only Discord path.
n8n also runs a Claude Haiku news digest each morning: HN + a Romanian RSS, merged, summarized, flagged on single-source claims.
Every backup/restore-drill script still pings its own healthchecks.io check on success, /fail on error.
Backup
Only back up what can’t be rebuilt from Git — and only what’s actually worth protecting. Home Assistant’s VM backup used to ride the offsite leg too; doesn’t anymore, not worth the bandwidth.
Longhorn (K8s) ──nightly──▶ Garage S3 on R730xdOracle VPS ──nightly──▶ R730xd (/media/backups/oracle-vps) │ ┌───────────────┴───────────────┐ │ weekly (Sunday) │ nightly ▼ ▼ Synology Oracle Cloud VPS (local-ish recovery) (restic, open format)
- Longhorn → Garage (R730xd), nightly. Jellyfin + *arr configs only — observability/cache volumes excluded, regenerable.
- Oracle VPS → R730xd, nightly. Authentik/Joplin dumps, Guacamole, Traefik certs, Pi-hole, Homepage, Portainer — plain SSH rsync, no daemon.
- R730xd → Synology, weekly. Whole
/media/backupstree, hardlink-deduplicated snapshots, 3 weeks retained. Same LAN — not a real offsite copy. - R730xd → Oracle, nightly, direct. Used to hop through Synology’s Hyper Backup, which needed a working DSM instance to restore. Replaced with restic over SFTP: open format, restorable with just the binary + password. Verified nightly (
restic check) and proven monthly with an actual restore-and-diff drill. Full writeup →
Three independent copies (R730xd, Synology, Oracle). Losing any one is a non-event; losing two still recovers, worst case from Synology’s weekly copy. Total loss requires all three gone at once: two on-prem devices plus a VPS in a different country.