- docs/STRUCTURE.md: add a .planning/ registry (vision/roadmap/history/breakdown
roles), broaden the roadmap role, add enforcement items 8-10.
- tools/check_requirements.py: scan .planning/ too - every .planning/**.md must be
registered, registered files must exist, requirement rows rejected there.
- .planning/: PROJECT.md, ROADMAP.md, MILESTONES.md, and milestones/v0.5/M3/
{roadmap.md,task_breakdown.md}. M3 breakdown holds only the tasks; decisions live
in the contracts.
- docs/DEPLOYMENT.md: add the ops runbook (registered as ops).
5.1 KiB
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-credentialson 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(branchmain). The pre-existing~/SlabUploaderon 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 (docs/contracts/PROMPTS.md); no prompt files ship with the image.
-
Push the branch to Gitea from the dev box.
-
On the UAT host:
cd ~/slab-deploy git fetch origin && git checkout <branch> && 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 -
Verify with the smoke checks in §5.
.env values
Generate on the UAT host, never in the repo:
SLAB_AES_KEY—openssl rand -base64 32WOO_BASE_URL— the store,https://only. FastAPI refuseshttp://at startup.APP_HOSTNAME— fromdeploy/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:
uvicornfrombackend/once the M3 scaffold lands; needs the same env vars as §3. - Tests: frontend
npm test(Playwright config infrontend/); 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):
curl -fsS http://127.0.0.1:<frontend-port>/api/healthon the host returns ok.- Phone browser opens the inventory URL (
deploy/INVENTORY.md) and the app loads. docker compose -f deploy/docker-compose.yml --env-file deploy/.env psshows both services up;docker logsforfastapishows no startup refusal (WOO_BASE_URL ishttps://).test-wooin 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
docker compose -f deploy/docker-compose.yml --env-file deploy/.env downon the UAT host.- Check out the last known-good ref in
~/slab-deployand re-run §3 steps 2–3. - Re-run §5. Volumes
slab_dbandslab_imagespersist 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 |