151 lines
4.5 KiB
Markdown
151 lines
4.5 KiB
Markdown
# 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:
|
|
```bash
|
|
# 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:
|
|
```bash
|
|
docker compose --env-file .env up -d
|
|
```
|
|
|
|
Teardown a single service:
|
|
```bash
|
|
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:
|
|
```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/<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:
|
|
```bash
|
|
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
|
|
```bash
|
|
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.
|