143 lines
5.5 KiB
Markdown
143 lines
5.5 KiB
Markdown
# 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 `خطای سرور`)
|