Files
SlabUploader/docs/DEPLOYMENT.md
T
grokbot_ty 33ce86c2e9 docs: unslop DEPLOYMENT store URL and Woo password setup
Drop issue-number pointers so a person can follow deploy/auth without the tracker.
2026-09-02 00:36:31 +00:00

9.1 KiB
Raw Blame History

Deployment Playbook (Docker Compose + Caddy)

Status: SPEC · env-agnostic · 2026-09-01 · SlabUploader

Target: any inventoried Docker host. Fill deploy/INVENTORY.md before bind. Variables: APP_HOST, APP_HOSTNAME, FRONTEND_PORT, FASTAPI_PORT. Health URL: https://${APP_HOSTNAME}/api/health.

Gate A freeze (2026-08-31): hybrid client owns measure/crop SoT. Do not scaffold a server ruler/pipeline as authoritative. Woo UAT = draft + SLAB-UAT-* on PROD. Inference is not on the app host by default. It is a remote configurable endpoint.

Historical lab example (non-normative): TyUBUMini at 192.168.1.201 with hostname slab.tyubumini.local was one early candidate. Do not treat that IP or name as SoT. Use the inventory worksheet.

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

Before deploying, fill deploy/INVENTORY.md (hard rule: inventory before execute):

  1. docker ps — existing containers, published ports (avoid collisions with co-resident apps; bolt.diy is one known example of 8443-class port use).
  2. Caddy config location (host file or container) and existing site blocks.
  3. Free port choice for the compose stack (proposal: 18080 http, 18443 https — verify free on this host).
  4. Docker volume dir convention on the host.
  5. Reachability: confirm ${APP_HOSTNAME} is reachable from the phone network (same LAN or Tailscale). Add DNS (Pi-hole/AdGuard or Tailscale MagicDNS) if needed: one A record → ${APP_HOST} (LAN and/or Tailscale name).

Write the chosen values into deploy/INVENTORY.md and the host .env before compose up.

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)
│   ├── INVENTORY.md               # filled at Gate B on the designated host
│   ├── .env.example               # SLAB_AES_KEY, APP_*, WOO_BASE_URL (https only)
│   └── backup.sh                  # sqlite .backup + images tar, with retention
├── frontend/                      # SvelteKit app
└── backend/                       # FastAPI app
    ├── app/
    │   ├── main.py
    │   ├── (no happy-path pipeline SoT here)  # hybrid: TECH-SPEC modules live in frontend/ or shared pure TS; backend = store/Woo/inference/U2Net stub
    │   ├── woo/                   # Woo client, taxonomy, publish
    │   ├── inference/             # vision + content wrappers (toggleable)
    │   ├── prompts/               # call1.txt, call2.txt (shipped defaults)
    │   ├── 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 (checked in)

Canonical file: deploy/docker-compose.yml. Placeholders: deploy/.env.example. From repo root:

cp deploy/.env.example deploy/.env && chmod 600 deploy/.env
# fill SLAB_AES_KEY, APP_HOSTNAME, WOO_BASE_URL (https:// only)
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d

Caddy site block: deploy/Caddyfile.fragment (merge into host Caddy). Compose service names frontend and fastapi must match that fragment.

Store URL (WOO_BASE_URL):

  • Set in deploy/.env and injected into the FastAPI container at start.
  • HTTPS only. FastAPI must refuse missing / http:// values at startup.
  • Not a Settings input and not a SQLite settings row.
  • Settings UI shows the URL as read-only text (which store this deploy hits).
  • Change store = edit .env + recreate the fastapi container.

Woo Application Password stays out of compose. Enter it in Settings; FastAPI stores it AES-GCM encrypted.

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)

Canonical file: deploy/Caddyfile.fragment. API must win over the frontend catch-all. Put handle /api/* before the frontend handle block:

{$APP_HOSTNAME} {
    tls internal            # Caddy internal CA (or host-appropriate TLS)
    handle /api/* {
        reverse_proxy fastapi:8000
    }
    handle {
        reverse_proxy frontend:80 {
            header_up X-Real-IP {remote_host}
        }
    }
}
  • HTTPS enforced (http→https redirect automatic when using Caddy TLS).
  • If the host Caddy already serves a wildcard for this domain family, add this as a site block. Verify the cert is trusted on the phone.
  • LAN/Tailscale only — no public exposure for POC.

5. Secrets

Secret Where Notes
SLAB_AES_KEY deploy/.env (mode 600) → compose env 32-byte base64; NEVER in the repo
WOO_BASE_URL deploy/.env → compose → FastAPI env https:// only; Settings read-only
Woo WP username + application password SQLite settings (AES-GCM) via Settings UI dedicated low-priv WP user; not consumer keys
Inference API key SQLite settings (AES-GCM) via Settings UI usually empty for local llama.cpp
.env.example repo placeholders only, committed

Woo credential setup (once per store):

  1. Store must already be on HTTPS. WordPress will not offer Application Passwords over plain HTTP.
  2. Create a dedicated low-privilege WordPress user (shop manager, or custom role with product create/edit + media upload). Do not reuse an admin login.
  3. Sign in as that user → Users → Profile → Application Passwords → generate a password for "SlabUploader".
  4. Enter the username + application password in the app Settings UI. FastAPI stores them AES-GCM encrypted. Browser never keeps them.
  5. Rotate by revoking the old application password in the WP profile and re-entering a new one in Settings, then test-woo.

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 and restore

deploy/backup.sh (run via cron on the app host, 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. cp -r /backups/prompts-YYYYMMDD.tar.gz prompts/              # prompt files
4. retain: 7 daily + 4 weekly; prune older
5. log result to /var/log/slab-backup.log; alert (Hermes) on failure
  • Backup target: local dir first; mirror to an off-box share for safety (same pattern as other stacks on the host).
  • 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://${APP_HOSTNAME}/api/health
Rekey AES §5
Rotate Woo creds WP user profile → revoke old application password → new one → Settings UI → POST /api/v1/settings/test-woo
Rotate inference key Settings UI (if external)
Edit a prompt Edit the text file on host, no restart needed (read fresh each time)
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 https://${APP_HOSTNAME}/api/health → ok (from phone over HTTPS once CA trust is proven)
  2. Settings: WP application password + test-woo ok; woo_create_status=draft; inference OFF for early gates
  3. Create a slab → upload test photos (fixture set) → poll to ready
  4. Verify bdft/price match the fixture's expected values (golden test; bdft = sqft × thickness)
  5. Publish test slab with SKU SLAB-UAT-… → slab may show local published; Woo product must be draft
  6. Leave or delete the Woo draft UAT product (never customer-visible). Final live publish is a non-UAT SKU after Ty review.

9. Scaling path (not v1)

  • Postgres when SQLite write contention appears (schema already portable).
  • Redis+arq when in-process queue shows lag.
  • GPU on the app host only if on-demand U2Net latency becomes a bottleneck. Keep CPU first. Never lower output_px_min below 1600; undersized source means retake, not a smaller min. RemBG is not part of the hybrid happy path.