Homelab
Self-hosted infrastructure on a Synology NAS: reverse proxy, SSO, DNS, monitoring, WireGuard tunnel, and automated deployment.
It started with a full Google Drive. Rosie and I were both hitting 100 GB limits, streaming services were adding ads to already-paid tiers, and the cost of doing nothing was quietly going up. A Synology DS923+ NAS fixed the storage problem. Then the question became: what else can you pull back from third-party services while you’re at it?
The current stack runs on that NAS in London, with a Hetzner VPS in Helsinki as a public WireGuard endpoint. Traefik handles routing and TLS. Authentik is the identity provider (Google OAuth upstream), so one login gets you into everything. Pi-hole runs split-horizon DNS. Prometheus, Grafana, and Loki cover observability. The whole thing is version-controlled in a private GitHub repo; deployment is git pull on the NAS, which triggers a post-merge hook that copies configs, injects secrets, and restarts whatever changed.
What I find interesting about it
The WireGuard setup solved a problem I didn’t expect. Cloudflare Tunnel, which I’d used first, has a file size limit on proxied traffic. That ruled out self-hosting photos and video. A £3.19/month Hetzner VPS acting as a dumb TCP proxy (no TLS termination, no inspection) gives you a public IP you control and no restrictions. Traffic travels through the WireGuard tunnel; TLS terminates on the NAS. The VPS never sees plaintext. The other surprise was throughput: the Synology kernel (4.4.x) has no WireGuard module, so it runs wireguard-go in userspace. Default settings gave 5 Mbps. After MTU tuning and UDP buffer fixes: 450 Mbps. That’s enough for remote Plex clients to direct-play original files, which sidesteps the hardware transcoding limitation entirely.
The infrastructure-as-code setup turned out to matter more than I expected. The post-merge hook isn’t just docker compose up: it does checksum comparisons to skip unnecessary Astro builds, manages container state selectively, and injects secrets at deploy time so credentials never touch the repo. Once that was in place, debugging shifted from “what did I change?” to “which commit broke it?” That’s a different category of problem.
The full build log (19 entries from the initial trigger through to the current state) is documented in the homelab journey.
Related writing
Stack
graph TB
subgraph ext["Internet"]
User(["External User"])
VPS["Hetzner VPS: nginx TCP proxy"]
end
subgraph lan["Home Network"]
Router["Router"]
subgraph nas["Synology NAS (DS923+)"]
WireGuard["WireGuard"]
Traefik["Traefik :443"]
CrowdSec["CrowdSec"]
PiHole["Pi-hole :53"]
subgraph containers["Containers"]
Authentik["Authentik"]
Grafana["Grafana"]
Prometheus["Prometheus"]
Portainer["Portainer"]
Uptime["Uptime Kuma"]
Immich["Immich"]
end
end
subgraph havm["Home Assistant VM"]
HA["Home Assistant"]
end
Client(["LAN Client"])
end
User -- HTTPS --> VPS
VPS -- WireGuard tunnel --> WireGuard
WireGuard --> Traefik
Traefik -- stream --> CrowdSec
Client -- DNS --> PiHole
Client -- HTTPS --> Traefik
Router -- DNS --> PiHole
Traefik --> Authentik
Traefik --> Grafana
Traefik --> Prometheus
Traefik --> Portainer
Traefik --> Uptime
Traefik --> Immich
Traefik --> HA