# Homelab Stack Standards ## Repository Root `/mnt/docker-ssd/docker/compose` Remotes: - GitHub: `git@github.com:Paccoco/truenas-stacks.git` - Gitea: `https://gitea.paccoco.com/fizzlepoof/truenas-stacks.git` ## 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/`) ## 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 cd /mnt/docker-ssd/docker/compose/ 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 # pushes to both GitHub and Gitea ``` All `.env` files are gitignored. Only compose files, `.env.example`, and init scripts are committed.