Files
SlabUploader/docs/DEPLOYMENT.md
T
Hermes Agent bee3bcc278 docs: full implementation plan (PRD decisions locked + specs 1-4 + impl plan)
- PRD: 20 locked decisions, open items cleared, fence fixed
- docs/TECH-SPEC-PIPELINE.md: deterministic math + 9 test vectors (deliverable #1)
- docs/ARCHITECTURE.md, DATA-MODEL.md, OPENAPI.md (deliverable #2),
  CONTENT-WOO.md, DEPLOYMENT.md (deliverable #4), IMPL-PLAN.md
- README rewritten; .gitignore added

AGENTS.md pending Ty's approval (protected file write was blocked).
2026-08-24 12:27:20 -07:00

6.8 KiB
Raw Blame History

Deployment Playbook (.201, Docker Compose + Caddy)

Status: SPEC (deliverable #4) · 2026-08-24 · SlabUploader Target: TyUBUMini at 192.168.1.201. Domain: slab.tyubumini.local (LAN/Tailscale only — Decision). Inference is NOT on .201; it's a remote configurable endpoint (.202 or elsewhere).

Note: exact Caddyfile/ports to be verified against the live .201 Caddy setup at implementation time (bolt.diy already runs Caddy TLS on tyubumini.local:8443). This playbook is the plan; the first implementation task is a .201 inventory pass.

1. Host inventory (do first — do not assume)

Before deploying, record the current state (HARD RULE: inventory before execute):

  1. docker ps — existing containers, published ports (avoid collisions; bolt.diy owns 8443/8444-class ports).
  2. Caddy config location (host file or container) and existing tyubumini.local site blocks.
  3. Free port choice for the compose stack (proposal: 18080 http, 18443 https — verify free).
  4. Docker volume dir convention on the host.
  5. Tailscale: confirm slab.tyubumini.local is reachable from the phone's network (same LAN or Tailscale). If the domain isn't in DNS yet, add it (Pi-hole/AdGuard or Tailscale MagicDNS) — one record, A → 192.168.1.201 (LAN) + Tailscale name.

2. Repository layout (at scaffold)

SlabUploader/
├── AGENTS.md
├── README.md
├── docs/                          # this plan set
├── deploy/
│   ├── docker-compose.yml
│   ├── Caddyfile.fragment         # slab site block (merged into host Caddy)
│   ├── .env.example               # SLAB_AES_KEY, port vars (no real secrets)
│   └── backup.sh                  # sqlite .backup + images tar, with retention
├── frontend/                      # SvelteKit app (Phase 1)
└── backend/                       # FastAPI app (Phase 0)
    ├── app/
    │   ├── main.py
    │   ├── pipeline/              # deterministic core (constants.py, ruler, geometry, widths, pricing, normalize)
    │   ├── woo/                   # Woo client, taxonomy, publish
    │   ├── inference/             # vision + content wrappers (toggleable)
    │   ├── api/                   # routers (slabs, photos, pricing, taxonomy, settings)
    │   ├── db/                    # schema.sql, models, migrations (alembic)
    │   └── security/              # AES-GCM settings encryption
    ├── tests/                     # pytest — unit (deterministic) + integration (woo)
    └── Dockerfile

3. docker-compose.yml (target shape)

services:
  frontend:
    build: ./frontend
    ports: ["127.0.0.1:${FRONTEND_PORT:-18080}:80"]   # Caddy-only; not public
    depends_on: [fastapi]
    restart: unless-stopped

  fastapi:
    build: ./backend
    environment:
      SLAB_AES_KEY: ${SLAB_AES_KEY}        # from .env (32-byte base64)
      # no Woo/inference keys here — they live encrypted in SQLite (Settings UI)
    volumes:
      - slab_db:/data
      - slab_images:/images
    ports: ["127.0.0.1:${FASTAPI_PORT:-18081}:8000"]  # Caddy-only
    mem_limit: 2g                            # env convention: mem_limit preferred
    restart: unless-stopped

volumes:
  slab_db:
  slab_images:

Conventions (house rules):

  • mem_limit on services (not host-level tuning).
  • Services bind to 127.0.0.1 only; Caddy is the only public surface.
  • No latest tags on base images — pin versions.
  • restart: unless-stopped; compose is the supervisor (no systemd units).

4. Caddy (fragment)

slab.tyubumini.local {
    tls internal            # Caddy internal CA (matches bolt.diy pattern)
    reverse_proxy frontend:80 {
        header_up X-Real-IP {remote_host}
    }
    handle /api/* {
        reverse_proxy fastapi:8000
    }
}
  • HTTPS enforced (http→https redirect automatic).
  • If the host Caddy already serves *.tyubumini.local, add this as a site block; verify the internal CA cert is trusted on the phone (same flow as bolt.diy).
  • LAN/Tailscale only — no public exposure.

5. Secrets

Secret Where Notes
SLAB_AES_KEY .env on host (mode 600) → compose env 32-byte base64; NEVER in the repo
Woo consumer key/secret SQLite settings (AES-GCM) via Settings UI rotate in Woo → re-enter in UI
Inference API key SQLite settings (AES-GCM) via Settings UI usually empty for local llama.cpp
.env.example repo placeholders only, committed

Key rotation (AES): SLAB_AES_KEY change requires re-encrypting settings — provide python -m app.security.rekey NEW_KEY (reads old key from env SLAB_AES_KEY_OLD, decrypts, re-encrypts, updates). Documented in playbook §7.

6. Backup & restore

deploy/backup.sh (run via cron on .201, e.g. daily 03:00):

1. sqlite3 /data/slab.db ".backup /backups/slab-YYYYMMDD.db"   # WAL-safe
2. tar czf /backups/slab-images-YYYYMMDD.tgz -C /images .
3. retain: 7 daily + 4 weekly; prune older
4. log result to /var/log/slab-backup.log; alert (Hermes) on failure
  • Backup target: local dir first; mirror to TrueNAS SMB share (\\truenas.local\…\backups\slab) for off-box safety (same pattern as other stacks).
  • Restore: stop compose → restore DB file + untar images → start compose. Tested at least once during Phase 6 (UAT) before declaring backup done.

7. Operations runbook

Task Command / how
Deploy (first) inventory §1 → docker compose -f deploy/docker-compose.yml build → up -d → Caddy reload → smoke §8
Update git pull → docker compose build fastapi frontend → up -d (DB migrations run on start via alembic)
Logs docker compose logs -f fastapi / frontend
Health curl -k https://slab.tyubumini.local/api/health
Rekey AES §5
Rotate Woo creds Woo UI → new consumer key/secret → Settings UI → POST /api/settings/test-woo
Rotate inference key Settings UI (if external)
Rollback image git checkout <tag> → rebuild → up -d; DB down-migrations provided for schema changes
Reset one slab delete its photos dir + UPDATE slabs SET status='draft' (admin script)

8. Smoke test (after every deploy)

  1. GET /api/health → ok
  2. Settings: Woo creds configured + test-woo ok
  3. Create a slab → upload test photos (fixture set) → poll to ready
  4. Verify bdft/price match the fixture's expected values (golden test)
  5. Publish test slab → published, Woo product visible as pending in store admin
  6. Delete/leave the pending test product as a staging artifact (never published)

9. Scaling path (not v1)

  • Postgres when SQLite write contention appears (schema already portable).
  • Redis+arq when in-process queue shows lag.
  • GPU on .201 only if RemBG latency becomes a bottleneck — otherwise keep it CPU (normalization budget is 3–5s per PRD; U²Net on modern CPU meets that).