Files
truenas-stacks/docs/operations/STACK_STANDARDS.md
Fizzlepoof f2e751e1e6
Some checks failed
secret-guardrails / artifact-secret-scan (push) Has been cancelled
secret-guardrails / gitleaks (push) Has been cancelled
docs: update github remote owner to fizzlepoof
2026-05-25 21:33:32 +00:00

4.5 KiB

Homelab Stack Standards

Repository Root

PD live compose root: /mnt/docker-ssd/docker/compose

NOMAD exception: standalone live deployments belong under /opt/<service> rather than a shared /mnt/docker-ssd/docker/compose tree.

Remotes:

  • GitHub: git@github.com:fizzlepoof/truenas-stacks.git
  • Gitea web: http://10.5.30.6:3002/fizzlepoof/truenas-stacks
  • Gitea SSH: ssh://git@10.5.30.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/<service>.

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/<service>/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:

# PD-style shared compose root
cd /mnt/docker-ssd/docker/compose/<stack>
docker compose --env-file .env config

# NOMAD-style service root
cd /opt/<service>
docker compose --env-file .env config

Deploy:

docker compose --env-file .env up -d

Teardown a single service:

docker compose --env-file .env down <service>

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:

networks:
  ix-databases_shared-databases:
    external: true

pangolin

Created by newt at runtime. Any externally exposed service must declare:

networks:
  pangolin:
    external: true

Storage Standards

Local SSD: /mnt/docker-ssd

Use /mnt/docker-ssd/docker/appdata/<service> 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/<service> 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:

sudo docker exec <container> sh -c "which curl; which wget" 2>&1
  • Has curl: ["CMD-SHELL", "curl -sf http://127.0.0.1:<port>/ || exit 1"]
  • Has wget only: ["CMD-SHELL", "wget -qO- http://127.0.0.1:<port>/ >/dev/null 2>&1 || exit 1"]
  • Has neither: ["CMD-SHELL", "cat /proc/net/tcp6 | grep -q <HEX_PORT> || exit 1"]
  • Redirecting endpoint: ["CMD-SHELL", "curl -sfL http://127.0.0.1:<port>/ || 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

cd /mnt/docker-ssd/docker/compose
git add .
git commit -m "description"
git push ssh://git@10.5.30.6:2222/fizzlepoof/truenas-stacks.git main

All .env files are gitignored. Only compose files, .env.example, and init scripts are committed.