Proxmox on OptiPlex 7040s - clustering, self-hosting, and why Traefik plus Caddy won

Proxmox on three OptiPlex 7040s - LubeLogger, Manyfold, Matrix, and a Traefik + Caddy reverse-proxy stack.

By early 2026 the side projects on this site - stagea-stuff, heff-shop, forums, wikis, blogs - all had the same dependency: somewhere honest to run them. Not a random VPS with a credit card attached to surprise egress. Owned hardware, owned backups, owned failure modes.

Project dossier (hardware BOM, timeline, deliverables): Proxmox Homelab Cluster.

That became a Proxmox cluster built from Dell OptiPlex 7040 small-form-factor boxes: cheap on the used market, quiet enough for a closet, and just powerful enough to pretend they are "real servers" if you are polite about RAM limits.

This post is the infrastructure layer behind the software posts: what got clustered, what got hosted, and why reverse proxies turned into a two-level Traefik + Caddy design instead of a single flat Caddyfile.

The hardware - OptiPlex 7040s in a cluster

Three 7040s, roughly matched:

  • 6th-gen Intel - i5 variants, not data-center cores, but fine for homelab duty
  • 16–32 GB RAM each - the real bottleneck; Proxmox will let you overcommit until reality arrives
  • SSD boot + larger SSD/HDD for VM disks - local local-lvm on every node; no fancy Ceph budget
  • Dual NICs where possible - one for management, one for VM/migration traffic (even on a flat network, separating helps debugging)

Proxmox clustering here is pragmatic, not heroic:

GoalWhy three nodes
HA mindsetIf one box dies, VMs can restart elsewhere (with caveats on local storage)
Workload splitChat/Matrix on one node, media/catalogue on another, reverse proxy + lightweight apps on a third
QuorumThree-node cluster avoids the two-node fence problem

Honest limitation: with local storage only, you do not get live migration of running VMs for free. Cluster membership still helps: centralized management, shared user auth, coordinated backups, and cold failover after a node loss. These nodes act as a scheduling pool, not a mini-AWS.

What runs on the cluster

The workload mix is intentionally boring-in-a-good-way: mostly Linux VMs and LXC containers running Docker Compose stacks, not Kubernetes cosplay.

LubeLogger

LubeLogger: vehicle maintenance logs, fuel, reminders. It exists for real vehicle maintenance tracking, not homelab aesthetics. Small Postgres or SQLite backend, low CPU, high "if this dies oil-change reminders disappear" value.

Manyfold

Manyfold - self-hosted 3D model library. STL/3MF archives, tagging, previews. Natural fit next to mechanical work and the 3D models side of this site. Storage-hungry, CPU-spiky during indexing - gets its own VM with disk headroom.

Matrix (Synapse + Element)

Federated chat - Synapse homeserver, Element web client, supporting Postgres and Redis. Matrix is the service that teaches you RAM is not negotiable. It runs, but it wants a dedicated node and aggressive log rotation.

Everything else

The long tail that justifies the cluster existing:

  • NodeBB / Ghost / MediaWiki test instances mirroring stagea-stuff layouts
  • Saleor experiments from heff-shop on a VM with Docker
  • Keycloak (or similar) OIDC experiments for SSO across subdomains
  • Uptime Kuma, Homepage, Vaultwarden, backup targets, and the inevitable Pi-hole-adjacent DNS VM

None of these are exotic. The challenge is twenty modest services all believing they deserve port 443.

Phase 1 - Caddy-only reverse proxy

The stack started where most homelab builds start: one Caddy on a small LXC, automatic HTTPS, one Caddyfile, smile.

Caddy is excellent when:

  • One edge box terminates TLS
  • Each backend is a simple reverse_proxy app.internal:3000
  • You control DNS and internal routing

Caddy became painful when:

Too many moving backends

Twenty apps means twenty hostname blocks, twenty health-check philosophies, and twenty different container networks that all use the word web and mean different things.

Split horizons

Public DNS points at the house. Internal clients sometimes need the same hostname to resolve differently. Caddy can do it; the Caddyfile stops being readable.

Matrix federation is picky

Synapse wants well-formed .well-known routes, _matrix paths, and consistent TLS at the edge. One mis-ordered handle block and remote homeservers treat you like a typo.

Docker socket exposure

The "Caddy watches Docker labels" pattern is convenient until you realize the proxy container often gets Docker socket access - a root-equivalent skeleton key. Fine for one homelab VM; uncomfortable as the entire edge security model.

Multi-node ambiguity

With three Proxmox nodes, where does the edge live? One LXC is correct. But some apps run on node B while Caddy runs on node A. Fine - until you automate and forget which internal IP moved after a rebuild.

Caddy stayed in the stack; one flat Caddyfile could not remain the entire control plane.

Phase 2 - Traefik at the edge, Caddy close to the apps

The settled design is two-tier:

Internet → Traefik (edge LXC, :443)
         → per-VM or per-stack Caddy (TLS optional / internal)
         → Docker Compose services

Traefik - cluster-wide front door

Traefik on a dedicated edge LXC (pinned to one node with a known public IP):

  • Owns public DNS hostnames and Let's Encrypt (DNS-01 where HTTP-01 is annoying)
  • Reads Docker labels only on an internal Docker socket proxy - not raw socket on the edge if avoidable
  • Routes *.heff.world, matrix.*, manyfold.*, lube.*, etc. to the correct internal Caddy or directly to apps when simple
  • Central place for middleware - IP allowlists for admin panels, rate limits on Matrix federation endpoints, basic auth on staging

Traefik thinks in routers, services, middlewares. That maps better to "thirty apps" than a monolithic text file.

Caddy - local terminator inside each VM

Each heavier VM (Matrix, Manyfold, Saleor test stack) keeps a local Caddy:

  • Terminates HTTP inside the VM's Docker network
  • Handles the messy per-app path rules Synapse and Saleor generate
  • Allows copying upstream Compose examples with minimal edits ("just put Caddy in front")
  • Rebuild one stack without touching cluster-wide edge config

Traefik sees one backend: http://10.0.10.12:80 (Caddy on the Matrix VM). Caddy sees the six containers Synapse actually needed. When Synapse docs say "put this path on your reverse proxy," edits target local Caddy, not the edge.

Why both

LayerJob
TraefikPublic names, TLS at the border, federation-facing correctness, global policy
CaddyPer-stack ergonomics, fast iteration, upstream-compose-friendly defaults

It is more moving parts. It is also fewer 2 a.m. edits to the one file that takes down everything.

Proxmox-specific challenges

Clustering OptiPlexes adds ops work unrelated to HTTP:

  • Quorum and corosync - three nodes means one failure is survivable; two simultaneous failures is data-loss theater
  • Backup strategy - vzdump to NAS on a schedule; test restores, not just backups
  • RAM accounting - Matrix + Manyfold indexing + Saleor Postgres on one node is a recipe for swap death
  • Thermal and power - three SFF boxes in a shelf need airflow; they are not loud until they are thermal-throttling
  • IP planning - management on 10.0.0.0/24, VM traffic on 10.0.10.0/24, document which VM owns which address

Lessons learned

  1. Start with three nodes if you want HA psychology - two-node Proxmox clusters teach fencing, not joy
  2. Put the edge on one LXC - Traefik or Caddy, pick an edge; do not run three edges
  3. Accept two proxy tiers when app count > ~10 - edge for names, local for path chaos
  4. Give Matrix its own VM - sharing with Manyfold indexing is how hobbies become outages
  5. LubeLogger and Vaultwarden are the canaries - if those backups restore cleanly, the cluster is real

Tie-in to this site

heff.world is still built for Netlify in production, but the self-hosted mirror of experiments - forums, shop staging, Matrix rooms for project coordination - lives on this cluster. The site build log mentioned wanting a self-hosting showcase; this is the rack in the basement backing that claim.

Software posts describe repos. This post describes where they run when rent-a-VM is not the answer.

More homelab write-ups (backup restores, Matrix federation DNS, Saleor on Proxmox) can follow if useful. For now: three OptiPlex 7040s, one Proxmox cluster, Traefik at the door, Caddy in the rooms, and LubeLogger for scheduled oil-change reminders.

Entradas relacionadas