Services: What Runs Where and How It’s Exposed
Concrete list of what I run, which host/VM it’s on, and how nginx and TLS are configured.
Reverse proxy (nginx) — single active node
- Primary: Ubuntu VM on Host 1. Fixed IP + keepalived VIP. Runs nginx, certbot, keepalived (MASTER), and an inotify-based sync. Dynamic DNS updater runs on a systemd timer.
- Secondary: (offline — Host 2 decommissioned 2026-04-11) Secondary nginx, keepalived BACKUP, and DDNS updater are all unavailable until a replacement node is provisioned.
- Config: Main homelab vhosts live in
conf.d/reverse-proxy.conf(all upstreams and mostserverblocks). Additional snippets can exist underconf.d/*.conf(e.g. a separate static site with its own TLS lineage). Mainnginx.confonly does process/events/http, TLS defaults (1.2/1.3, modern ciphers), andinclude conf.d/*.conf. - Sync: Config and cert sync to secondary is currently disabled (secondary nginx offline — Host 2 decommissioned 2026-04-11). Primary nginx alone holds the VIP and serves all traffic.
nginx documentation · keepalived
DNS — Pi-hole × 2 (active)
- Instance 1: Ubuntu VM on Host 1 (Pi-hole’s web admin is proxied at e.g.
pihole01.detellem.com, LAN-only via nginxallow/deny). - Instance 2: TrueNAS Scale app on Host 3 (same IP as the NAS, different port). Proxied at e.g.
pihole02.detellem.com, LAN-only. - Instance 3: (offline — Host 2 decommissioned 2026-04-11) Pi-hole 3 VM unavailable.
Two Pi-holes plus two Google DNS fallbacks are in the DHCP “DNS servers” list. Pi-hole admin is not exposed to the internet; nginx returns 403 for those LAN-only hostnames.
Main landing — Homepage
- Where: Docker container on the Docker VM (Host 1).
- Role: Dashboard with links to all my services (Plex, Bitwarden, Mealie, etc.). Served at the root domain and at
www,home,dashboard,homepagesubdomains (one nginx server block, same backend). - Config: YAML for widgets and services; I version the config in the homelab repo and mount it into the container.
Plex
- Where: Host 1 (Windows), not in a VM — same machine as Hyper-V and Ollama. Previously Host 2 until that machine was decommissioned 2026-04-11.
- How it’s exposed:
plex.detellem.comthrough nginx (TLS at the proxy) to the Plex port on Host 1. - Media: Libraries on TrueNAS (Host 3) over SMB; GPU (RTX 3070) for hardware transcoding where enabled.
Bitwarden (self-hosted)
- Where: Docker Compose stack on the Docker VM (Host 1). Multiple containers (nginx, api, identity, web, mssql, etc.); the Bitwarden “nginx” container is the internal front door, and my nginx reverse proxy forwards to it.
- Exposed as:
bitwarden.detellem.com. Client max body size increased in nginx for attachments. - Data: Persistent volumes for database and attachments; backups are manual/scheduled outside the container.
Mealie (recipes)
- Where: Single Docker container on the Docker VM. One port (e.g. 9000) mapped on the host.
- Exposed as:
recipes.detellem.comandmealie.detellem.com(same server block). - Data: Docker volume; backup as needed.
Donetick (chores)
- Where: Docker container on the Docker VM.
- Exposed as:
chores.detellem.comanddonetick.detellem.com.
Donetick (or current repo)
IT-Tools
- Where: Docker container on the Docker VM. Stateless.
- Exposed as:
it-tools.detellem.com.
ConvertX (file conversion)
- Where: Docker container on the Docker VM. Needs a larger
client_max_body_sizein nginx for uploads (e.g. 1G). - Exposed as:
xconvert.detellem.comandconvertx.detellem.com.
c4illin/convertx (or current)
OpenWebUI (LLM chat)
- Where: Docker container on the Docker VM (Host 1). Backend: Ollama running on the Host 1 Windows machine using the local NVIDIA GPU.
- Exposed as:
ai.detellem.com,llm.detellem.com. Built-in auth (email/password); WebSocket and streaming-safe nginx config. (chat.detellem.comwas repurposed for Stoat Chat — see below.)
Stoat Chat (self-hosted Discord alternative)
- Where: Dedicated Ubuntu VM on Host 1 (Hyper-V). 15 containers (API, web frontend, voice via LiveKit, MongoDB, file storage via MinIO, message broker via RabbitMQ, etc.).
- Exposed as:
stoat.detellem.comandchat.detellem.com. nginx reverse proxy for HTTP/WebSocket; nginx stream proxy for LiveKit voice/video (TCP + UDP). Invite-only registration; CincySMP-branded frontend. - Backups: Automated daily (MongoDB dump, MinIO data, config) to TrueNAS NFS; 7 daily + 4 weekly retention.
Minecraft (game server)
- Where: Dedicated Ubuntu VM on Host 1 (Hyper-V). Not a Docker container — runs as a native systemd service with its own 6 vCPUs and 32GB RAM.
- Exposed as:
cincysmp.detellem.comon port25565(raw TCP, not HTTPS). Nginx’sstreammodule on the primary proxy VM forwards the connection and passes the real client IP via PROXY protocol. - Security: Mojang authentication (online-mode), whitelist-only, fail2ban connection-flood jail on the proxy, UFW on the VM allowing only the proxy IP.
TrueNAS (storage)
- Where: Host 3. Not exposed through the reverse proxy to the internet. I access the web UI from the LAN only. SMB shares are used by Host 1 and other devices. Datasets hold media, backups, and app data as needed.
Summary table
| Service | Host/VM | Exposed as | Notes |
|---|---|---|---|
| Homepage | Docker VM (H1) | detellem.com, www, home… | Main dashboard |
| Plex | Host 1 (Windows) | plex.detellem.com | Media on Host 3 (SMB) |
| Bitwarden | Docker VM (H1) | bitwarden.detellem.com | Compose stack |
| Mealie | Docker VM (H1) | recipes / mealie | Single container |
| Donetick | Docker VM (H1) | chores / donetick | Single container |
| IT-Tools | Docker VM (H1) | it-tools.detellem.com | Stateless |
| ConvertX | Docker VM (H1) | xconvert / convertx | Large uploads |
| OpenWebUI | Docker VM (H1) | ai/llm.detellem.com | LLM chat; Ollama on H1 GPU |
| Stoat Chat | H1 VM (dedicated) | stoat/chat.detellem.com | Self-hosted Discord alt; voice via LiveKit |
| Minecraft | H1 VM (dedicated) | cincysmp.detellem.com:25565 | Paper 1.21.11; TCP via nginx stream |
| Pi-hole 1–2 | H1 VM, H3 app | pihole01/pihole02.detellem.com | LAN-only; Pi-hole 3 offline |
| nginx | H1 VM (primary); H2 offline | — | VIP, TLS; secondary offline |