Homelab
A production-style lab, run at home.
My homelab is a small Proxmox environment managed with production habits: declarative roles, vault-managed secrets, local firewalling, reverse proxying, monitoring, backups, dependency updates, and an off-site monitor that is outside the same failure domain as the services it watches.
A Proxmox node running Debian VMs on VLANs segmented by the Ubiquiti gateway.
Forgejo and CI runner, Vaultwarden, Home Assistant, Pi-hole, Immich, Frigate, Traccar, TimescaleDB, Redis, MQTT, OpenCloud, Firefly III, Uptime Kuma, and this site.
Ansible roles, Docker Compose per VM, ansible-vault secrets, restic backups to an off-site Hetzner Storage Box, Renovate, CrowdSec, Authentik, and VictoriaMetrics-based observability.
Decisions that matter
Same stack as production
- VictoriaMetrics, VMAgent, VMAlert, Alertmanager, Grafana, Loki, node-exporter, cAdvisor.
- Freshness, availability, and host health are treated as monitored signals, not dashboard decoration.
- External Uptime Kuma and ntfy run on Oracle Cloud A1.Flex so alerts survive home internet outages.
GitOps-style Ansible
- One role per service; Docker Compose is the runtime contract per VM.
- Secrets live in ansible-vault; service config is templated, reviewed, and redeployed from code.
- A Renovate bot watches every base image and dependency, opening a pull request with the changelog whenever a new version ships, so updates get reviewed, not missed.
Defense in depth
- Ubiquiti Cloud Gateway owns the edge: local firewalling, VLAN segmentation, WireGuard VPN, and explicit inter-network rules.
- Cloudflare is DNS only. No Cloudflare Tunnels sit in front of the lab.
- Traefik terminates wildcard TLS; admin paths stay behind WireGuard, internal routing, CrowdSec, and Authentik where appropriate.
CI proves the loop
- Forgejo runs on the apps VM with an Actions runner backed by isolated Docker-in-Docker.
- The runner builds the static Astro image for this site and rolls it onto the homelab app host.
- The public page is deliberately simple: static files in nginx, no client JavaScript, observable like any other service.
Shape of the system
DNS only
firewall + VLANs
TLS + routing
roles + vault
per VM
alerts + logs
The local side is split by responsibility. The infra VM carries ingress and dynamic DNS. The monitoring VM carries the metrics and alerting stack. The apps VM carries Forgejo, the runner, databases, utilities, and this website. Immich and Frigate are isolated because photos and NVR workloads have different storage and CPU needs.
DNS is deliberately not a single point of failure. Pi-hole runs in two places, on the Proxmox environment and on a standalone Raspberry Pi, so name resolution keeps working even when the Proxmox host is down for maintenance.