# Homelab Stack Standards ## Repository Root PD live compose root: `/mnt/docker-ssd/docker/compose` NOMAD exception: standalone live deployments belong under `/opt/` rather than a shared `/mnt/docker-ssd/docker/compose` tree. Remotes: - GitHub: `git@github.com:Paccoco/truenas-stacks.git` - Gitea web: `http://10.5.1.6:3002/fizzlepoof/truenas-stacks` - Gitea SSH: `ssh://git@10.5.1.6:2222/fizzlepoof/truenas-stacks.git` Use the internal Gitea SSH remote from PD/NOMAD. Port `2222` is SSH, not HTTP. ## Stack Layout Each production stack lives in a subdirectory of the repo root. Each stack contains: - `docker-compose.yaml` — required - `.env` — required (gitignored) - `.env.example` — committed with placeholder values - optional init scripts (e.g. `initdb/`, `initdb-mariadb/`) ## NOMAD Live Services On NOMAD, live service roots belong under `/opt/`. This includes: - host-run services - standalone Docker Compose stacks - small service-local runtimes that should stay self-contained Examples: - `/opt/project-nomad` - `/opt/newt` - `/opt/doris-dashboard` - `/opt/pihole-nomad` Default rule on NOMAD: keep runtime data with the service under `/opt//data` unless a service has a documented reason to use another storage tier. Use the repo for source/staging, but do not run production services from an agent workspace or random checkout under `/home/fizzlepoof/.openclaw/workspace`. ## Deployment Order 1. `databases/` — must be up before anything else 2. `infrastructure/` — newt creates the pangolin network 3. All other stacks in any order ## Deployment Workflow Validate before deploying: ```bash # PD-style shared compose root cd /mnt/docker-ssd/docker/compose/ docker compose --env-file .env config # NOMAD-style service root cd /opt/ docker compose --env-file .env config ``` Deploy: ```bash docker compose --env-file .env up -d ``` Teardown a single service: ```bash docker compose --env-file .env down ``` ## Networks ### ix-databases_shared-databases Created by the `databases` stack with `name: ix-databases` at top level. Any stack connecting to shared databases must declare: ```yaml networks: ix-databases_shared-databases: external: true ``` ### pangolin Created by `newt` at runtime. Any externally exposed service must declare: ```yaml networks: pangolin: external: true ``` ## Storage Standards ### Local SSD: `/mnt/docker-ssd` Use `/mnt/docker-ssd/docker/appdata/` for: - Postgres / MariaDB / Redis data - SQLite-backed apps - Apps that chown aggressively on startup - GPU/model caches - Write-heavy logs, cache, state ### TrueNAS Pool (tank): `/mnt/tank` Use `/mnt/tank/docker/appdata/` for: - General appdata - Configs - Uploads - Non-permission-sensitive state ### Unraid NFS: `/mnt/unraid` - `/mnt/unraid/data/media/` — media libraries - `/mnt/unraid/immich` — Immich photo/video storage ## Volume Rules - no anonymous volumes - no ad-hoc home directory paths - all binds use standardized production paths - evaluate SSD vs tank vs Unraid before deploying any new service - apps known to have permission issues on NFS go on SSD ## Known Permission Quirks - `shlink` data directory must be chmod 777 — runs as non-root user - `immich-ml` cache goes to `/mnt/docker-ssd/docker/appdata/immich-ml` (separate from immich-server) - `donetick` requires `/mnt/tank/docker/appdata/donetick/config/selfhosted.yaml` — env vars alone insufficient ## Secrets Management - `.env` files are gitignored - `.env.example` files are committed with placeholder values - Database passwords: `openssl rand -hex 24` - JWT secrets: `openssl rand -hex 32` ## Healthcheck Patterns Always check available tools before writing healthchecks: ```bash sudo docker exec sh -c "which curl; which wget" 2>&1 ``` - Has curl: `["CMD-SHELL", "curl -sf http://127.0.0.1:/ || exit 1"]` - Has wget only: `["CMD-SHELL", "wget -qO- http://127.0.0.1:/ >/dev/null 2>&1 || exit 1"]` - Has neither: `["CMD-SHELL", "cat /proc/net/tcp6 | grep -q || exit 1"]` - Redirecting endpoint: `["CMD-SHELL", "curl -sfL http://127.0.0.1:/ || exit 1"]` Common hex port values: 8080=1F90, 3003=BBB, 3000=BB8, 8181=1FF5, 5055=13BF, 2021=7E5 After changing a healthcheck, must `down` then `up` (not just restart). ## Git Workflow ```bash cd /mnt/docker-ssd/docker/compose git add . git commit -m "description" git push ssh://git@10.5.1.6:2222/fizzlepoof/truenas-stacks.git main ``` All `.env` files are gitignored. Only compose files, `.env.example`, and init scripts are committed.