System overview
«PROJECT» is a self-hosted service stack on one Hetzner dedicated server running Proxmox VE. Services run in LXC containers plus one QEMU VM (the isolated devvm — inventory). Everything is reached over Tailscale — there are no public service endpoints.
The three layers
1. Host
Hetzner dedicated baremetal («PUBLIC_IP») running Proxmox VE 9.x as node
«HOSTNAME». See inventory for hardware and topology for the
host network config. The host itself is joined to the tailnet — that’s
the management path; the Proxmox UI is never exposed publicly.
2. Internal network
Guests live on 10.0.1.0/24 behind SDN bridge vnet0 (gateway
10.0.1.1), reaching the internet via SNAT out vmbr0 to «PUBLIC_IP».
Inbound access is not NAT’d — there is no port forwarding at all.
3. Access path
All service access enters over Tailscale:
- HTTP services → the
caddyLXC (on the tailnet) terminates TLS and reverse-proxies to internal10.0.1.xaddresses. - git-over-SSH → the
giteaLXC is itself tailnet-joined. - DNS →
«MYDOMAIN»resolves via split-horizon: tailnet clients send those queries totechnitiumdnsat «TECHNITIUM_TAILNET_IP».
https://wastebin.«MYDOMAIN» — request flow
1. DNS client split-horizon → technitium @«TECHNITIUM_TAILNET_IP»
answers «CADDY_TAILNET_IP»
2. TLS caddy :443 — real Let's Encrypt cert via Cloudflare DNS-01
3. proxy caddy → http://10.0.1.22:8088 (plain HTTP on the LAN side)
Invariants
These hold everywhere in the system — docs and changes should preserve them:
- No public service exposure. No public A/AAAA records for
«MYDOMAIN»services, no port forwards, no public Proxmox UI — enforced twice: the Hetzner Robot firewall drops new inbound connections at the provider edge (topology), and the host has no NAT/port-forwards anyway. - Single HTTP ingress. Caddy is the only reverse proxy; every HTTP
service gets a
«MYDOMAIN»subdomain pointing at «CADDY_TAILNET_IP». - Real TLS certs despite zero public records — Let’s Encrypt via Cloudflare DNS-01 challenges.
- Small tailnet footprint. Only machines that need to be on the tailnet are joined (host, caddy, technitiumdns, gitea). No subnet routing.
- Helper scripts by default. LXCs come from Proxmox community helper-scripts unless there’s a reason not to.
- SSO where supported. Pocket-ID (backed by LLDAP) is the identity provider.
Layer-by-layer detail
| Area | Doc |
|---|---|
| Host hardware + guest table | inventory |
| Bridges, subnet, NAT | topology |
| DNS zones, records, split-horizon | dns |
| Tailnet membership + sharing model | tailscale |
| Storage pools + Storage Box | storage |
| Every service + how it’s reached | catalog |
| Auth stack | pocket-id, lldap |
Design history
Authentik → replaced by Pocket-ID. Rootless-podman service VMs → experimented with, not adopted. See decisions.