Back to projects
personal

Homelab

Self-hosted infrastructure on a Synology NAS: reverse proxy, SSO, DNS, monitoring, WireGuard tunnel, and automated deployment.

Docker Traefik WireGuard Prometheus Grafana Authentik See the journey

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.

Stack

Docker Docker
Traefik Traefik
WireGuard WireGuard
Authentik Authentik
Prometheus Prometheus
Grafana Grafana
Loki Loki
CrowdSec CrowdSec
Pi-hole Pi-hole
Immich Immich
Home Assistant Home Assistant
Portainer Portainer
Uptime Kuma Uptime Kuma
Nginx Nginx
Cloudflare Cloudflare
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