Files

143 lines
5.5 KiB
Markdown
Raw Permalink Normal View History

# Trial deploy — 6 active lessons (no public launch checklist)
This document prepares a **trial** stack using the existing Docker Compose layout. It does **not** deploy to a real server from this repo.
## Architecture (default)
```
Browser → [TLS terminator — your choice] → frontend nginx :80
├─ / → Vue SPA (try_files → index.html)
├─ /api/* → proxy → backend Nest :3000
└─ /health → proxy → backend /health
Backend → SQLite file on named volume (single instance)
```
| Component | Image / build | User |
|-----------|---------------|------|
| Frontend | `frontend/Dockerfile` → nginx:alpine | nginx (non-root worker) |
| Backend | `backend-nest/Dockerfile` → node:22.18.0-slim | `node` |
| Data | Docker volume `child-gold-land-sqlite` → `/app/data/app.db` | writable by `node` |
**Not supported without proof:** SQLite on NFS/network shares, horizontal multi-instance backend with this rate limiter.
## Placeholders (set on server — never commit)
Copy `frontend/.env.docker.example` → `frontend/.env.docker`:
| Variable | Example placeholder |
|----------|---------------------|
| `JWT_SECRET` | `<generate-48+-char-random>` |
| `CORS_ORIGIN` | `https://<your-trial-host>` or `http://<host>:8080` |
| `VITE_API_URL` | `/api` (keep for same-origin nginx proxy) |
| `FRONTEND_PORT` | `8080` |
### Proxy trust boundary (compose default)
| Variable | Compose default | Meaning |
|----------|-----------------|--------|
| `TRUST_PROXY` | `true` | Use `X-Real-IP` / `X-Forwarded-For` **only** when TCP peer is in `TRUST_PROXY_CIDRS` |
| `TRUST_PROXY_CIDRS` | Explicit list in compose (trial) | **Production:** set the CIDR of the nginx→backend hop (e.g. `docker network inspect child-gold-land`); backend **refuses** `TRUST_PROXY=true` without `TRUST_PROXY_CIDRS` when `NODE_ENV=production` |
- **Fastify `trustProxy` stays off** — client IP for rate limits comes from `getClientIp()` (trusted peer + single-hop headers).
- nginx sets `X-Forwarded-For $remote_addr` (replaces forged client chains).
- **Backend is not published on the host** — only frontend port is exposed; direct access to Nest bypasses nginx and should not be exposed in trial deploy.
**Rate limiting:** in-memory, **per client IP**, **single backend instance**. Many users behind one NAT share one bucket. For stricter limits use an edge proxy or account-based throttling (out of scope).
**Do not** expose backend port `3000` on the host in production trial.
## Build and start (local trial)
From sibling clones (`frontend/`, `backend-nest/`):
```powershell
cd frontend
copy .env.docker.example .env.docker
# Edit .env.docker — set JWT_SECRET and CORS_ORIGIN
docker compose config # validate compose file
docker compose build
docker compose up -d
```
Checks:
```powershell
curl http://localhost:8080/health
curl http://localhost:8080/health/ready
curl http://localhost:8080/api/... # via app UI login
```
Vue routes: refresh on `/map`, `/market`, etc. must return `index.html` (configured in `nginx.conf`).
## HTTPS
- Terminate TLS at **your** reverse proxy (Caddy, Traefik, nginx on host) or cloud LB.
- Redirect HTTP→HTTPS at the terminator.
- Set `CORS_ORIGIN` to the **exact** browser origin (`https://host`, no trailing slash).
- Do not generate real certificates in this repo.
## Persistent data
- **Path in container:** `/app/data/app.db` (+ `-wal`/`-shm` when WAL mode active)
- **Volume:** `child-gold-land-sqlite` (compose)
- **Host permissions:** ensure volume UID/GID allows `node` (uid 1000 in image) to read/write; if bind-mounting a host dir: `chown 1000:1000 <dir>`
Startup **never** replaces an existing DB with seed data. Migrations run forward-only on boot; failure aborts startup.
## Backup and restore
On backend repo (host or one-off container with volume mounted):
```powershell
cd backend-nest
$env:DB_PATH = "/path/to/app.db" # or container path
$env:BACKUP_DIR = "/path/to/backups"
$env:BACKUP_RETENTION_DAYS = "14"
npm run backup:sqlite
npm run restore:sqlite -- backups/app-....db /tmp/restored.db
# Stop backend first. Restore to a sibling path (not live DB_PATH), then swap files — see backend-nest/docs/restore-production-db.md.
# RESTORE_ACK_STOPPED=YES = operator ack only (not automatic stop detection).
```
Uses SQLite **backup API** (WAL-safe), then `integrity_check`.
## Health vs readiness
| Endpoint | Meaning |
|----------|---------|
| `GET /health` | Liveness — process up |
| `GET /health/ready` | Readiness — DB migrated |
Compose `depends_on: service_healthy` uses **ready**.
## Rollback
| Action | Effect |
|--------|--------|
| `docker compose pull` + run previous image tags | Rolls back **code** only |
| DB | **Not** rolled back with image — restore from backup if needed |
| Forward migrations | Newer code may require newer schema; rolling back code without DB restore can break — test rollback pairs in staging |
## Safe shutdown
```powershell
docker compose down
# Data remains in volume child-gold-land-sqlite
```
## CI backend pin (E2E)
No silent `main` fallback: set Gitea variable **`E2E_BACKEND_REF`** to backend **commit SHA**. See `e2e/backend.contract.json` and frontend README.
Secrets (names only): `E2E_BACKEND_CHECKOUT_TOKEN`.
## Still required for real deploy
- [ ] Registered Gitea **act_runner** (CI not verified here)
- [ ] Trial host OS, domain, TLS certificates
- [ ] Strong `JWT_SECRET` on server
- [ ] Off-site backup storage
- [ ] Monitoring / log aggregation (no stack traces to clients — API returns generic `خطای سرور`)