Some 45 services (ever growing number) on Proxmox, standardized into a single Compose template pattern. Three years, two full migrations, and the groundwork for moving the whole thing to Kubernetes.


homepage dashboard


currently
traefik
Reverse proxy
wg-easy
WireGuard VPN
authentik
Identity provider / SSO
crowdsec
Intrusion detection
adguard
DNS ad blocking
jellyfin
Media server
plex
Media server (alt)
jellyseer
Media request manager
radarr
Movie management
sonarr
TV show management
sonarr-anime
Anime TV management
bazarr
Subtitle management
prowlarr
Indexer manager
qbittorrent
Torrent client
flaresolverr
Cloudflare bypass
huntarr
Media hunting automation
profilarr
Quality profile manager
immich
Photo management / Google Photos alt
frigate
NVR / camera AI detection
homepage
Dashboard
portainer
Docker management UI
komodo
Container/deployment manager
grafana
Metrics dashboards
prometheus
Metrics collection
scrutiny
Disk health monitoring
mealie
Recipe manager
copyparty
File sharing server
kiwix
Offline content
thelounge
IRC web client
myspeed
Speed test tracker
crafty
Minecraft server manager
glances
System monitor
btop
Resource monitor
ncdu
Disk usage analyzer
metubedl
yt-dlp web UI

the hardware

Nothing fancy. The whole thing runs on a single Proxmox VE node:

  • CPU: AMD Ryzen 5 1600 – six cores, twelve threads, first-gen Zen. Ancient by 2026 standards and still more than enough.
  • RAM: 40 GB DDR4.
  • Boot / root: NVMe with ZFS.
  • Bulk storage: 1.8 TB SSD (/ssd) + 16 TB HDD for media and backups.
  • Network: MikroTik RB2011 as the edge router, TP-Link TL-SG108E for the switch, self-terminated Cat6 running through the walls.

Of course, this is my previous desktop PC, converted to a fun thing to play with.

This is my previous desktop PC. Before this homelab existed, it played games. Now it serves me photos and catches packets. I like that arc.

why self-host at all

Because cloud subscriptions add up, because I learn more in a weekend of breaking things than a month of tutorials, and because I want my photos, my notes, my media, and my passwords to live on hardware I can physically touch. The honest answer is that it's fun. Both are true.

I started this in 2023 on an old desktop I wasn't using anymore. It hasn't stopped growing since.

how it's laid out

Proxmox hosts two LXC containers that do 95% of the work:

  • CT for web / public stuff: CloudPanel, running the Ghost instances for hexie.dev and imeanit.nl.
  • CT for the Docker stack: everything else – all ~45 services, one docker-compose.yml to rule them. (get that ref?)
  • a few more CTs for testing (like openclaw, dont expose this lil guy!)
  • a VM for the future k3s homelab

The rest of the node runs smaller utility CTs (backup targets, test environments, the occasional throwaway VM when I'm trying something destructive).

the stack

Grouped by what they actually do for me:

Entry and identity Traefik reverse-proxies everything with automatic Let's Encrypt certs. Authentik sits in front as the SSO / identity provider. WireGuard (via wg-easy) is the only way in from outside. CrowdSec watches the logs and blocks the inevitable background noise, the public internet is a very loud place.

DNS AdGuard Home, because DNS-level ad blocking is the single biggest quality-of-life upgrade you can give a household network.

Media Jellyfin is the main server; Plex is there as a backup for family members who refuse to install a new app. Jellyseerr handles requests. The arr stack does its thing: Radarr for movies, Sonarr for TV, a second Sonarr for anime, Bazarr for subtitles, Prowlarr as the indexer manager, qBittorrent as the client, and FlareSolverr when Cloudflare gets in the way. Huntarr and Profilarr handle automation and quality profiles.

Photos and files Immich for photos, the Google Photos replacement I didn't know I was allowed to have. Paperless-ngx for documents. Copyparty for quick file sharing. Vaultwarden for passwords.

Home and cameras Frigate as the NVR, doing object detection on the camera feeds. Recently upgraded from jsmpeg to MSE/WebRTC via go2rtc, which made the live views actually usable.

Dashboards and monitoring Homepage as the landing dashboard. Prometheus scrapes, Grafana visualizes, Loki stores the logs, Uptime Kuma pings everything and yells when something dies. Scrutiny watches the drives. Glances and btop for when I want to SSH in and look at numbers.

Small things I use constantly Mealie for recipes. Mealie is my favourite underdog in this whole stack. Komodo and Portainer for when I want a UI on top of Docker. Kiwix for offline Wikipedia. The Lounge for IRC. MySpeed to track whether my ISP is lying to me (they are). Crafty to spin up a Minecraft server when friends feel nostalgic. MeTube for yt-dlp.

Mail SMTP is handled by Brevo, used by basically everything in the stack that needs to send a notification. I set it up once, and every service that can take an SMTP host now reaches me the same way.

standardization work

This is the part I'm actually proud of.

For the first two years, my docker-compose.yml was an archaeological dig. Every service had a slightly different label style, different env var conventions, different ways of declaring the Traefik router. It worked, but adding a new service meant copy-pasting a hundred lines from whichever existing service looked closest and then grep-replacing names until things stopped breaking.

So I sat down and standardized the whole thing around three YAML anchors:

x-traefik-labels: &traefik-labels
  traefik.enable: "true"
  traefik.http.routers.SERVICE.rule: "Host(`sub.domain.tld`)"
  traefik.http.routers.SERVICE.entrypoints: "websecure"
  traefik.http.routers.SERVICE.tls.certresolver: "letsencrypt"
  traefik.http.services.SERVICE.loadbalancer.server.port: "PORT"

x-homepage-labels: &homepage-labels
  homepage.group: "GROUP"
  homepage.name: "NAME"
  homepage.icon: "ICON"
  homepage.href: "URL"

x-kuma-labels: &kuma-labels
  uptime-kuma.http.name: "NAME"
  uptime-kuma.http.url: "URL"

Every service then pulls from the relevant anchors and overrides what it needs to. Each service also has its own .env file so secrets and per-service config never leak into the main compose file or into git.

The gotcha is that Docker Compose interpolates environment variables inside label values but not inside label keys. You can't do traefik.http.routers.${SERVICE}.rule. The router key has to be hardcoded per service. The rest of the label can use env vars freely, but that one part has to be spelled out. I know this because I spent far too long wondering why Traefik was silently ignoring half my routes.

The whole thing now lives at github.com/hexiejexie/homelab, with a HOMELAB.md documenting the structure and a Mermaid architecture diagram in the README.

two things that broke recently

Proxmox kernel upgrade to 6.17.x. Went smoother than expected. ZFS snapshot before, reboot, pray, and everything came back. The snapshot is the only reason I'm willing to touch the kernel on a machine that's serving things I care about.

CT 1337 backup failure. One of my container backups started failing because the target pool didn't have enough space. The fix ended up being embarrassingly simple: I added /ssd (the 1.8 TB SSD) as a new Proxmox Directory storage target and pointed the backup job at it. Done. The lesson, as always, is to actually read the error message instead of assuming the problem is more complicated than it is.

what next

The whole reason I standardized everything into a consistent Compose template was to prepare for the real move: Kubernetes. Specifically k3s on Proxmox, with FluxCD handling GitOps, and the whole stack migrated service by service in six phases starting with the stateless stuff (arr apps, Homepage, the dashboards), then monitoring, then media, then the stateful services (Immich, Paperless, Vaultwarden), and finally flipping the DNS and retiring the Compose file.

That migration deserves its own post, or probably several. I'll write them as I go, the same way I'm writing this one after the fact, once I know which parts worked and which parts I regret.


If you're thinking about starting your own homelab: don't plan it first. Plug in the box, install Proxmox, spin up Jellyfin, and let it grow from there. Every single service in my stack got added over time because I wanted a specific thing that week, not because I drew up an architecture diagram in advance. The architecture came later, once the chaos demanded it.

The footer of this site reads "to a great mind, nothing is little". The homelab is mostly how I practice that.