# Deployment Role: ops. This is the canonical runbook for getting a build from a working tree to a live dev/test host. `deploy/INVENTORY.md` is the only source for what runs where (hosts, ports, container names). `docs/UAT-PRACTICE.md` is the human walk once the app is up; this doc is what makes it up. ## 1. Topology | Node | Role | Reach | |---|---|---| | Ty dev box (Windows) | code, git push to Gitea. No Docker CLI. | LAN + Tailscale | | Gitea (`gitea.vps1.afterthedemo.com`) | source of truth for the repo. SSH port closed; HTTPS only. | Tailscale | | `ubudual5060` (UAT host, LAN `192.168.1.202`, Tailscale `100.64.0.9`) | builds and runs Docker images for UAT. | LAN + Tailscale | The dev box never builds images. The UAT host pulls source from Gitea and builds locally. Do not install Docker on the dev box to work around this; the pull-build path is the plan. ## 2. Access (already provisioned; do not re-do) - Dev box → UAT host: `ssh ubudual5060` (key `~/.ssh/ubudual5060`, wired in `~/.ssh/config`). Unattended, passphrase-less. - UAT host → Gitea: HTTPS with a read-only token named `5060-pull`, stored in `~/.git-credentials` on the host. The host clones/pulls with the plain HTTPS URL; no key material in the repo. - Deploy checkout on the UAT host: `~/slab-deploy` (branch `main`). The pre-existing `~/SlabUploader` on that host is a stale work clone — do not use it. If access is broken, fix it here before touching compose. Do not commit credentials to the repo. ## 3. Build and deploy (M3) Precondition: the M3 scaffold exists on the branch being deployed — `frontend/Dockerfile`, `backend/Dockerfile`, and `deploy/.env` on the host. `main` currently has `frontend/Dockerfile` only; `backend/` lands with M3 implementation. Prompts live in SQLite with all revisions, seeded from the text in `docs/contracts/PROMPTS.md` §6 on first run; no prompt files ship with the image (`METADATA-105`). 1. Push the branch to Gitea from the dev box. 2. On the UAT host: ``` cd ~/slab-deploy git fetch origin && git checkout && git pull cp deploy/.env.example deploy/.env && chmod 600 deploy/.env # first time only docker compose -f deploy/docker-compose.yml --env-file deploy/.env build docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d ``` 3. Verify with the smoke checks in §5. ### `.env` values Generate on the UAT host, never in the repo: - `SLAB_AES_KEY` — `openssl rand -base64 32` - `WOO_BASE_URL` — the store, `https://` only. FastAPI refuses `http://` at startup. - `APP_HOSTNAME` — from `deploy/INVENTORY.md`. - `FRONTEND_PORT` / `FASTAPI_PORT` — see the port override below. Woo Application Password and inference keys are not in `.env`; they are entered in the app's Settings and stored AES-GCM encrypted in the app's SQLite. ### Port binding for the dev-test environment `deploy/docker-compose.yml` binds `127.0.0.1` only (Caddy is the TLS edge in the full stack). The dev-test environment is reached directly on a port from the project block `10880`–`10889` (per `deploy/INVENTORY.md`) and **must be reachable from any machine on `192.168.1.0/24`**, so the deploy must override the port bindings to `0.0.0.0` (compose override file or `ports:` edit scoped to this deploy). Never bind `:10880` (Ty's local Node/SvelteKit) or Ty's `:18080`. M3 takes over port `10881` from the M2 container (`slabuploader-m2-overnight`), per the replace-prior-container rule in `deploy/INVENTORY.md`. Remove the M2 container when the M3 one is healthy. ## 4. Local loop (no UAT host needed) - Frontend: `cd frontend && npm install && npm run dev` (Vite). - Backend: `uvicorn` from `backend/` once the M3 scaffold lands; needs the same env vars as §3. - Tests: frontend `npm test` (Playwright config in `frontend/`); backend tests land with M3. Use the local loop for iteration; use §3 for anything Ty must see on a phone. ## 5. Smoke checks (after every deploy) Run from the UAT host, then from a phone on the same network (LAN or Tailscale): 1. `curl -fsS http://127.0.0.1:/api/health` on the host returns ok. 2. Phone browser opens the inventory URL (`deploy/INVENTORY.md`) and the app loads. 3. `docker compose -f deploy/docker-compose.yml --env-file deploy/.env ps` shows both services up; `docker logs` for `fastapi` shows no startup refusal (WOO_BASE_URL is `https://`). 4. `test-woo` in app Settings reports ok (store reachable, taxonomy synced). If a check fails, the deploy is not live. Roll back per §6 before debugging on the live port. ## 6. Rollback 1. `docker compose -f deploy/docker-compose.yml --env-file deploy/.env down` on the UAT host. 2. Check out the last known-good ref in `~/slab-deploy` and re-run §3 steps 2–3. 3. Re-run §5. Volumes `slab_db` and `slab_images` persist across rollback; do not remove them unless the data itself is the problem. ## Where each rule lives | Rule | Source | |---|---| | Hosts, ports, container names | `deploy/INVENTORY.md` | | Compose shape, env contract | `deploy/docker-compose.yml`, `deploy/.env.example` | | UAT walk once the app is up | `docs/UAT-PRACTICE.md` | | M3 scope and exit | `docs/MILESTONES.md`, `docs/REQUIREMENTS.md` | | Woo auth model | `AGENTS.md`, `docs/contracts/CONTENT-WOO.md` |