Running since 2024

What's actually running
on my home server

Host: Debian 12 Services: 25 in inventory Uptime: months, not weeks

Overview

One bare-metal Debian machine. Docker Compose stacks for everything container-shaped, native systemd services for the long-running things, Nginx in front of the public stuff, Tailscale in front of the private stuff. No Kubernetes. No orchestration layer. Just compose files in a git repo and a few well-placed systemd timers.

This page is a current inventory — what's actually running as of the last deploy, not aspirational architecture. I update the breakdown when I add a service, drop one, or kill something that didn't earn its keep.

The host

cpu
Intel i7-1255U
12 cores
memory
15 GB DDR4
~7 GB free
storage
922 GB
~16% used
os
Debian 12
kernel 6.x
nic
1 Gbps
behind NAT
public ip
none
Tailscale funnel only

Topology

bare-metal Debian host
 └─ docker compose stacks (compose files in ${HOME}/<stack>/)
    └─ homelab — pi-hole, audiobookshelf, uptime-kuma, tailscale, avahi
    └─ immich — server + ml + postgres (pgvecto-rs) + redis (valkey)
    └─ navidrome — music server + lidarr downloader sidecar
    └─ media-stack — sonarr / radarr / prowlarr / qbittorrent
    └─ homepage — service dashboard on :3002
 └─ systemd services (host-level)
    └─ nginx (reverse proxy)
    └─ postfix + dovecot (self-hosted mail)
    └─ samba (smbd / nmbd) — LAN file shares
    └─ dnsmasq (local DHCP + DNS caching)
    └─ netdata (live system telemetry)
    └─ tailscaled (mesh VPN, exit node)
    └─ tor@default (relay)
    └─ tv-bridge (custom Nest → Roku/FireTV script)
    └─ job-watcher (resume pipeline trigger)
 └─ side projects (venv + cron)
    └─ ai-core/main.py — local LLM helper
    └─ super-hub/hub.py — LAN device orchestration
    └─ tailor-tool/app.py — resume tailoring Flask app

Each Docker stack has its own docker-compose.yml, pinned image versions, named volumes for state, and a restart: unless-stopped policy. Compose files are version-controlled in GitHub so I can rebuild any stack from a fresh host in under 10 minutes.

Service inventory

media
serviceroleimage / source
Navidromeself-hosted music (Subsonic API)navidrome/navidrome
Audiobookshelfaudiobooks & podcastsaudiobookshelf/audiobookshelf
SonarrTV show automationlinuxserver/sonarr
Radarrmovie automationlinuxserver/radarr
Prowlarrindexer manager for *arrlinuxserver/prowlarr
qBittorrenttorrent client (LAN only)linuxserver/qbittorrent
Immichphoto & video backup (Google Photos replacement)immich-server + pgvecto-rs + valkey
network & dns
serviceroleimage / source
Pi-holenetwork-wide ad blocking DNSpihole/pihole
dnsmasqDHCP + local DNS cachingsystemd (host)
avahimDNS / Bonjour for LAN discoverysystemd (host) + docker
TailscaleWireGuard mesh + Funnel for public ingresstailscaled (host) + docker
Tor relaycontribute bandwidth to the Tor networktor@default (host)
SambaSMB/NFS file shares to LAN devicessmbd / nmbd (host)
infrastructure
serviceroleimage / source
Nginxreverse proxy, SSL termination, vhost routingnginx (host)
Postfix + Dovecotself-hosted mail (IMAP/Submission)postfix + dovecot2 (host)
Netdataper-second system telemetrynetdata (host)
Homepageservice dashboard (read-only status)gethomepage/homepage (docker)
Uptime KumaHTTP / TCP / DNS uptime checks + alertslouislam/uptime-kuma (docker)
NetAlertXLAN presence detection (who's home, by MAC)netalertx/netalertx (docker)
Home Assistanthome automation hub (configured, run on demand)homeassistant/home-assistant
side projects
serviceroleimage / source
ai-corelocal LLM helper for batch jobsPython venv (host)
super-hubLAN device orchestration & control planePython venv (host)
tailor-toolFlask app that tailors a resume to a JDPython venv (host)
tv-bridgeconvert Google Nest commands to Roku/FireTV IRPython systemd (host)
job-watcherInbox watcher that triggers the resume pipelinePython systemd (host)

How I expose things

Three ingress layers, used for different jobs:

Backups

Three layers, in order of how painful it would be to lose:

Things I actually learned

Self-hosting mail is a rite of passage

Postfix + Dovecot + DKIM + DMARC + reverse DNS + IP reputation. I expected it to be one weekend. It was three weekends, a support ticket to my ISP asking them to set PTR records, and four rounds of "why is Gmail marking this as spam." I still self-host because I want the muscle memory for SMTP, but I also now understand why almost nobody does.

Pi-hole in a container behind Tailscale works surprisingly well

The whole network's ad-blocking DNS now resolves through a container running on the host, with Tailscale as the only ingress for the admin UI. Means I can toggle blocklists from my phone without ever opening a port on the home router.

Compose volumes should match what you actually want to lose

First Immich rebuild, I forgot to mount the named volume for thumbnails onto the new container. Six months of thumbnails regenerated, but the originals were safe because they were on a separate volume. Naming matters. immich_dataimmich_thumbsimmich_postgres.

Monitoring you don't look at is not monitoring

Uptime Kuma sits on the dashboard. I check it daily. Netdata is too dense to stare at — I only look at it when something feels off, but the historical graphs have saved me at least twice when "feels off" turned into "was actually off for 40 minutes overnight."

The boring infra is what fails the loudest

Most outages I've had weren't an app crashing. They were a full /var partition (syslog not rotated), a stale Docker bridge, or a cert that the Let's Encrypt wild-card DNS challenge couldn't renew because dnsmasq was answering NXDOMAIN before the upstream. The fix is always unromantic: log rotation, journalctl --vacuum-size, cron monitor.

Caveats

What's next

Two specific things, both small:

← Back to Portfolio View GitHub ↗ Resume