Private
Public Access
Phase 8: Production deployment (Debian 13, Nginx Proxy Manager)
Deployment artifacts for the two-server architecture from ARCHITECTURE.md §9, grounded in verified Debian 13 (trixie) package facts (Python 3.13, PostgreSQL 17, Node.js 20, redis-server 8.0, certbot 4.0, ufw -- confirmed rather than guessed) rather than a generic "modern Linux" guide: deploy/systemd/chatapp.service, deploy/chatapp.env.example, deploy/backup-postgres.sh, deploy/upgrade.sh, and DEPLOYMENT.md as the actual numbered runbook. Revised mid-implementation once the user clarified the app sits behind an existing, separate Nginx Proxy Manager rather than local Nginx+certbot: dropped the local Nginx config entirely, gunicorn now binds a TCP port instead of a Unix socket, and app/main.py gained a static-file mount + SPA fallback route so gunicorn alone serves the built frontend, /api, and /ws on one port -- what lets NPM's simple one-upstream-per-domain mode work with zero custom path routing. Path-traversal-guarded (full_path comes straight from the URL) and cache-header-differentiated (far-future immutable on Vite's content-hashed assets, no-cache on index.html/sw.js/ manifest so a deploy actually propagates instead of leaving clients on a stale service worker) -- verified locally against a real gunicorn process serving a real frontend build, not just eyeballed. Two real gaps found and fixed alongside the docs, not just noted: gunicorn wasn't a dependency anywhere despite being the whole app-server design, and there was no WebSocket reconnect logic on the client -- a reverse proxy's idle-connection timeout (NPM's or otherwise) would have silently killed a quiet chat connection with nothing to recover it. Added exponential-backoff reconnect to useChatSocket.ts, verified by hand (killed and restarted the local dev backend mid-session, confirmed auto-reconnect and that a message sends successfully afterward with no page reload). Every command in DEPLOYMENT.md that could be verified locally, was: the exact systemd ExecStart line run against local dev Postgres/Redis with clean SIGTERM shutdown, the static-file serving behavior against a real build, both shell scripts syntax-checked. What couldn't be verified from this sandbox (actual Debian 13 hardware, Nginx Proxy Manager itself) is flagged explicitly in the plan rather than claimed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+21
-2
@@ -1,4 +1,4 @@
|
||||
# KeepItTalking backend (Phase 1 + 2 + 4 + 5 + 6 + 7)
|
||||
# KeepItTalking backend (Phase 1 + 2 + 4 + 5 + 6 + 7 + 8)
|
||||
|
||||
FastAPI + SQLAlchemy 2.0 (async) + PostgreSQL + Redis. Implements auth, room
|
||||
CRUD (open and private), room roles (owner/admin/member) and invites, a
|
||||
@@ -107,7 +107,8 @@ DATABASE_URL=postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp_test .v
|
||||
|
||||
```
|
||||
app/
|
||||
main.py create_app(), session middleware, router/WS mounting
|
||||
main.py create_app(), session middleware, router/WS mounting,
|
||||
serves frontend/dist if it exists (see below)
|
||||
config.py environment-driven settings (pydantic-settings)
|
||||
database.py async engine/session, get_db() dependency
|
||||
dependencies.py get_current_user (session cookie or Bearer token),
|
||||
@@ -128,6 +129,24 @@ alembic/ migrations
|
||||
tests/ pytest + httpx/TestClient tests
|
||||
```
|
||||
|
||||
## Production deployment (Phase 8)
|
||||
|
||||
See [`../DEPLOYMENT.md`](../DEPLOYMENT.md) for the full runbook. The one
|
||||
piece that lives in this backend's own code: `app/main.py` serves the built
|
||||
frontend directly (mounts `frontend/dist/assets` with far-future
|
||||
`Cache-Control` on Vite's content-hashed filenames, and a catch-all route
|
||||
that serves any other real file under `frontend/dist` or falls back to
|
||||
`index.html` for client-side routes like `/rooms/<id>` — `index.html`/
|
||||
`sw.js`/`manifest.webmanifest` always get `Cache-Control: no-cache` instead,
|
||||
since caching any of those is exactly how a client ends up stuck on a stale
|
||||
app version after a deploy) — but only if `frontend/dist` exists at
|
||||
startup. It never does in local dev (the Vite dev server handles the
|
||||
frontend there instead), so this is fully inert until someone actually runs
|
||||
`npm run build`. The point: one Gunicorn port ends up serving the frontend
|
||||
*and* `/api` *and* `/ws`, which is what lets a reverse proxy (Nginx Proxy
|
||||
Manager, in the deployment this was built for) forward a whole domain to a
|
||||
single upstream with no custom per-path routing.
|
||||
|
||||
## Admin portal (Phase 6)
|
||||
|
||||
Every `/api/admin/*` route (`app/routers/admin.py`) requires
|
||||
|
||||
Reference in New Issue
Block a user