5.5 KiB
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
trustProxystays off — client IP for rate limits comes fromgetClientIp()(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/):
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:
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_ORIGINto 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/-shmwhen 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):
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
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_SECRETon server - Off-site backup storage
- Monitoring / log aggregation (no stack traces to clients — API returns generic
خطای سرور)