/dev/nvme1n1 (Samsung 1TB) — OS drive
├── /dev/nvme1n1p1 → /boot/efi (1G, vfat)
└── /dev/nvme1n1p2 → / (953G, ext4)
/dev/nvme0n1 (SK hynix 1TB) — Data drive
└── /dev/nvme0n1p1 → /data (953G, ext4)
/dev/sdb (Samsung 990 PRO 1TB, ASM2462 USB-NVMe enclosure) — Backup drive
└── /dev/sdb1 → /mnt/backup (916G, ext4)
UUID=8795cb2e-fe34-4543-b93b-dd45b642846f
(SMART needs `-d sntasmedia`; the /dev/sdX name is not stable — see 06-drive-monitoring.md)
Note: NVMe device names (nvme0n1 vs nvme1n1) can occasionally swap between reboots. Always verify with lsblk before scripting against device names — use UUIDs in /etc/fstab.
Caddy — HTTPS reverse proxy for all web services (<svc>.z2mini.gabrielgabrie.com); auto-renewing Let's Encrypt certs via ACME DNS-01/Cloudflare
100.67.235.68 only
445
Samba SMB
all
2283
Immich web UI + mobile API
127.0.0.1 only (fronted by Caddy → immich.z2mini.gabrielgabrie.com)
3000
Homepage launcher dashboard
127.0.0.1 only (fronted by Caddy → home.z2mini.gabrielgabrie.com)
4533
Navidrome web UI + Subsonic API
127.0.0.1 only (fronted by Caddy → navidrome.z2mini.gabrielgabrie.com)
5232
Radicale CalDAV/CardDAV (HTTP)
127.0.0.1 only (fronted by Caddy → radicale.z2mini.gabrielgabrie.com)
8080
Vaultwarden web vault + API (HTTP, container)
127.0.0.1 only (fronted by Caddy → vault.z2mini.gabrielgabrie.com)
8082
OpenProject internal proxy (publishes the stack)
127.0.0.1 only (fronted by Caddy → openproject.z2mini.gabrielgabrie.com)
8090
Beszel hub web UI + agent ingest
127.0.0.1 only (fronted by Caddy → beszel.z2mini.gabrielgabrie.com)
2019
Caddy admin API (config / reload)
127.0.0.1 only (host loopback — Caddy runs network_mode: host)
No services exposed to the public internet. Caddy (port 443, bound to the Tailscale interface IP only, network_mode: host) is the single HTTPS ingress — it terminates TLS with auto-renewing Let's Encrypt certs obtained via the ACME DNS-01 challenge through the Cloudflare API, and reverse-proxies to each app on 127.0.0.1:<port>. Its config is all under /data/docker/caddy/ (Caddyfile + .env token) — unlike the old tailscale serve listeners it replaced, which lived in tailscaled's state. See 17-caddy.md. The two old tailscale serve HTTPS front-ends (:443 Radicale, :8443 Vaultwarden) are retired.
Heads-up: every app binds 127.0.0.1:<port> only — so scripts on z2mini can use http://127.0.0.1:<port> or http://localhost:<port> directly (the old "must use the tailnet IP" gotcha is gone). https://<svc>.z2mini.gabrielgabrie.com also works from the box. What no longer works: http://z2mini:<port> and http://100.67.235.68:<port> — those bindings were removed when the apps moved behind Caddy. From other tailnet devices, the only way in is https://<svc>.z2mini.gabrielgabrie.com (which requires the device's DNS to resolve *.z2mini.gabrielgabrie.com — see the Tailscale global resolvers note above).
Rails web app (Puma, 4–16 threads) — UI + REST API + Hocuspocus client
openproject-worker-1
OpenProject
openproject/openproject:17-slim
Background jobs — mail, exports, search index, notifications
openproject-cron-1
OpenProject
openproject/openproject:17-slim
Scheduled-jobs runner (daily/weekly maintenance)
openproject-seeder-1
OpenProject
openproject/openproject:17-slim
Runs db:migrate + db:seed on every up, then exits
openproject-db-1
OpenProject
postgres:17
Primary database
openproject-cache-1
OpenProject
memcached
Rails cache store
openproject-proxy-1
OpenProject
openproject/proxy (built locally)
Internal proxy — publishes the stack on 127.0.0.1:8082
openproject-hocuspocus-1
OpenProject
openproject/hocuspocus:17.4.0
WebSocket server for collaborative editing
openproject-autoheal-1
OpenProject
willfarrell/autoheal:1.2.0
Restarts web if its healthcheck fails (Docker socket RW)
Each stack defined under /data/docker/<stack>/docker-compose.yml. Status: cd /data/docker/<stack> && docker compose ps. (Caddy is a locally built image — docker compose build rebuilds it; see 17-caddy.md.)
Vaultwarden registration closed after the single account was created (SIGNUPS_ALLOWED=false); admin panel and SMTP left disabled
TOTP and passkeys to be consolidated into the Vaultwarden vault (decided May 2026; re-enroll off Microsoft Authenticator gradually; school/work accounts that mandate the Microsoft app stay on it)
Caddy as the single HTTPS reverse-proxy ingress (not Traefik, nginx, NPM); replaced the per-service tailscale serve listeners (May 2026)
All web apps bound to 127.0.0.1:<port> only; Caddy (network_mode: host, listening on 100.67.235.68:443 + :80) is the one door in — <svc>.z2mini.gabrielgabrie.com
Caddy image built locally = stock caddy + the caddy-dns/cloudflare plugin (needed for ACME DNS-01 — no public inbound, so HTTP-01/TLS-ALPN-01 unusable)
DNS for gabrielgabrie.com moved to Cloudflare (Hostinger still the registrar); *.z2mini wildcard A → 100.67.235.68; the Cloudflare API token (scoped Zone.DNS:Edit) lives in /data/docker/caddy/.env
Tailscale operator set to gabriel; Tailscale global resolvers 1.1.1.1+8.8.8.8 with "Override local DNS" (so tailnet devices resolve *.z2mini.gabrielgabrie.com — the housing router's DNS rejects/stale-caches it)
OpenProject chosen as the project-management / PMP-learning tool (not Planka / Vikunja / Kanboard / WeKan / Taiga / Redmine); upstream opf/openproject-deploystable/17 cloned verbatim into /data/docker/openproject/, customized only via .env. Bind-mounted postgres/ (uid 999 mode 700, never raw-rsync) + assets/ (uid 1000, files-as-truth) + backups/ (nightly pg_dump). Port 127.0.0.1:8082 fronted by Caddy at https://openproject.z2mini.gabrielgabrie.com. autoheal kept upstream-verbatim despite the Docker-socket-RW trade-off (single-user tailnet box; auto-restart on Rails OOM is useful).
These were considered carefully — see z2mini-context-for-ai.md for reasoning. Suggesting reversal requires a real reason.
Docker containers for Samba (rejected — runs better natively)
Traefik / nginx / Nginx Proxy Manager — Caddy chosen instead as the reverse proxy (smallest config, automatic HTTPS+renewal built in); see 17-caddy.md
TrueNAS / Unraid (rejected — designed for multi-drive RAID setups)
Snap packages (rejected — using Docker for future services instead)
PhotoPrism, Ente, Photoview (evaluated as Immich alternatives; Immich won on iPhone integration + ML)
CopyTrans Cloudly (evaluated for iCloud bulk download; rejected in favor of FOSS server-side icloudpd. Kept as Plan B insurance if icloudpd fails)
iCloud for Windows (evaluated for iCloud bulk download; rejected — needs 458 GB+ of free local laptop storage)
Official Bitwarden self-hosted ("unified" container) — rejected vs Vaultwarden (~1 GB+ RAM, bundled DB engine, a few features license-gated); identical clients either way
KeePassXC + .kdbx over the Samba share (evaluated as a Vaultwarden alternative — server-less, files-as-truth — but loses on iOS: third-party iOS apps, sync-conflict risk, clunkier autofill)
Standalone Bitwarden Authenticator app (TOTP-only; not needed — Vaultwarden's TOTP-in-vault is free)
Cloud-Mac rental for the iCloud Passwords export (MacinCloud trial attempted 2026-05-11; Apple's anti-fraud blocks iCloud sign-in from datacenter IPs — abandoned. Migration was eventually done on a trusted personal MacBook, 2026-05-22.)