Add deployment guide for staging Docker servers with DB backup/restore
Covers fresh server setup, environment configuration, database backup (full and per-tenant), restore into staged environment, migration execution, and verification steps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
375
docs/DEPLOYMENT.md
Normal file
375
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
# HOA LedgerIQ — Deployment Guide
|
||||||
|
|
||||||
|
**Version:** 2026.3.2 (beta)
|
||||||
|
**Last updated:** 2026-03-02
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
2. [Deploy to a Fresh Docker Server](#deploy-to-a-fresh-docker-server)
|
||||||
|
3. [Backup the Local Test Database](#backup-the-local-test-database)
|
||||||
|
4. [Restore a Backup into the Staged Environment](#restore-a-backup-into-the-staged-environment)
|
||||||
|
5. [Running Migrations on the Staged Environment](#running-migrations-on-the-staged-environment)
|
||||||
|
6. [Verifying the Deployment](#verifying-the-deployment)
|
||||||
|
7. [Environment Variable Reference](#environment-variable-reference)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
On the **target server**, ensure the following are installed:
|
||||||
|
|
||||||
|
| Tool | Minimum Version |
|
||||||
|
|-----------------|-----------------|
|
||||||
|
| Docker Engine | 24+ |
|
||||||
|
| Docker Compose | v2+ |
|
||||||
|
| Git | 2.x |
|
||||||
|
| `psql` (client) | 15+ *(optional, for manual DB work)* |
|
||||||
|
|
||||||
|
The app runs five containers — nginx, backend (NestJS), frontend (Vite/React),
|
||||||
|
PostgreSQL 15, and Redis 7. Total memory footprint is roughly **1–2 GB** idle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy to a Fresh Docker Server
|
||||||
|
|
||||||
|
### 1. Clone the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh your-staging-server
|
||||||
|
|
||||||
|
git clone <repo-url> /opt/hoa-ledgeriq
|
||||||
|
cd /opt/hoa-ledgeriq
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create the environment file
|
||||||
|
|
||||||
|
Copy the example and fill in real values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # or vi, your choice
|
||||||
|
```
|
||||||
|
|
||||||
|
**Required changes from defaults:**
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
# --- CHANGE THESE ---
|
||||||
|
POSTGRES_PASSWORD=<strong-random-password>
|
||||||
|
JWT_SECRET=<random-64-char-string>
|
||||||
|
|
||||||
|
# Database URL must match the password above
|
||||||
|
DATABASE_URL=postgresql://hoafinance:<same-password>@postgres:5432/hoafinance
|
||||||
|
|
||||||
|
# AI features (get a key from build.nvidia.com)
|
||||||
|
AI_API_KEY=nvapi-xxxxxxxxxxxx
|
||||||
|
|
||||||
|
# --- Usually fine as-is ---
|
||||||
|
POSTGRES_USER=hoafinance
|
||||||
|
POSTGRES_DB=hoafinance
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
NODE_ENV=development # keep as development for staging
|
||||||
|
AI_API_URL=https://integrate.api.nvidia.com/v1
|
||||||
|
AI_MODEL=qwen/qwen3.5-397b-a17b
|
||||||
|
AI_DEBUG=false
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Tip:** Generate secrets quickly:
|
||||||
|
> ```bash
|
||||||
|
> openssl rand -hex 32 # good for JWT_SECRET
|
||||||
|
> openssl rand -base64 24 # good for POSTGRES_PASSWORD
|
||||||
|
> ```
|
||||||
|
|
||||||
|
### 3. Build and start the stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Build the backend and frontend images
|
||||||
|
- Pull `postgres:15-alpine`, `redis:7-alpine`, and `nginx:alpine`
|
||||||
|
- Initialize the PostgreSQL database with the shared schema (`db/init/00-init.sql`)
|
||||||
|
- Start all five services on the `hoanet` bridge network
|
||||||
|
|
||||||
|
### 4. Wait for healthy services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
All five containers should show `Up` (postgres and redis should also show
|
||||||
|
`(healthy)`). If the backend is restarting, check logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs backend --tail=50
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. (Optional) Seed with demo data
|
||||||
|
|
||||||
|
If deploying a fresh environment for testing and you want the Sunrise Valley
|
||||||
|
HOA demo tenant:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres psql -U hoafinance -d hoafinance < db/seed/seed.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
- Platform admin: `admin@hoaledgeriq.com` / `password123`
|
||||||
|
- Tenant admin: `admin@sunrisevalley.org` / `password123`
|
||||||
|
- Tenant viewer: `viewer@sunrisevalley.org` / `password123`
|
||||||
|
|
||||||
|
### 6. Access the application
|
||||||
|
|
||||||
|
| Service | URL |
|
||||||
|
|-----------|--------------------------------|
|
||||||
|
| App (UI) | `http://<server-ip>` |
|
||||||
|
| API | `http://<server-ip>/api` |
|
||||||
|
| Postgres | `<server-ip>:5432` (direct) |
|
||||||
|
|
||||||
|
> **Note:** For production, add an SSL-terminating proxy (Caddy, Traefik, or
|
||||||
|
> an nginx TLS config) in front of port 80.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backup the Local Test Database
|
||||||
|
|
||||||
|
### Full database dump (recommended)
|
||||||
|
|
||||||
|
From your **local development machine** where the app is currently running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/HOA_Financial_Platform
|
||||||
|
|
||||||
|
# Dump the entire database (all schemas, roles, data)
|
||||||
|
docker compose exec -T postgres pg_dump \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
--no-owner \
|
||||||
|
--no-privileges \
|
||||||
|
--format=custom \
|
||||||
|
-f /tmp/hoafinance_backup.dump
|
||||||
|
|
||||||
|
# Copy the dump file out of the container
|
||||||
|
docker compose cp postgres:/tmp/hoafinance_backup.dump ./hoafinance_backup.dump
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--format=custom` flag produces a compressed binary format that supports
|
||||||
|
selective restore. The file is typically 50–80% smaller than plain SQL.
|
||||||
|
|
||||||
|
### Alternative: Plain SQL dump
|
||||||
|
|
||||||
|
If you prefer a human-readable SQL file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres pg_dump \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
--no-owner \
|
||||||
|
--no-privileges \
|
||||||
|
> hoafinance_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup a single tenant schema
|
||||||
|
|
||||||
|
To export just one tenant (e.g., Pine Creek HOA):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres pg_dump \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
--no-owner \
|
||||||
|
--no-privileges \
|
||||||
|
--schema=tenant_pine_creek_hoa_q33i \
|
||||||
|
> pine_creek_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Finding a tenant's schema name:**
|
||||||
|
> ```bash
|
||||||
|
> docker compose exec -T postgres psql -U hoafinance -d hoafinance \
|
||||||
|
> -c "SELECT name, schema_name FROM shared.organizations WHERE status = 'active';"
|
||||||
|
> ```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Restore a Backup into the Staged Environment
|
||||||
|
|
||||||
|
### 1. Transfer the backup to the staging server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp hoafinance_backup.dump user@staging-server:/opt/hoa-ledgeriq/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Ensure the stack is running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/hoa-ledgeriq
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Drop and recreate the database (clean slate)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to postgres and reset the database
|
||||||
|
docker compose exec -T postgres psql -U hoafinance -d postgres -c "
|
||||||
|
SELECT pg_terminate_backend(pid)
|
||||||
|
FROM pg_stat_activity
|
||||||
|
WHERE datname = 'hoafinance' AND pid <> pg_backend_pid();
|
||||||
|
"
|
||||||
|
docker compose exec -T postgres dropdb -U hoafinance hoafinance
|
||||||
|
docker compose exec -T postgres createdb -U hoafinance hoafinance
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4a. Restore from custom-format dump
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the dump into the container
|
||||||
|
docker compose cp hoafinance_backup.dump postgres:/tmp/hoafinance_backup.dump
|
||||||
|
|
||||||
|
# Restore
|
||||||
|
docker compose exec -T postgres pg_restore \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
--no-owner \
|
||||||
|
--no-privileges \
|
||||||
|
/tmp/hoafinance_backup.dump
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4b. Restore from plain SQL dump
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres psql \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
< hoafinance_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Restart the backend
|
||||||
|
|
||||||
|
After restoring, restart the backend so NestJS re-establishes its connection
|
||||||
|
pool and picks up the restored schemas:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart backend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Migrations on the Staged Environment
|
||||||
|
|
||||||
|
Migrations live in `db/migrations/` and are numbered sequentially. After
|
||||||
|
restoring an older backup, you may need to apply newer migrations.
|
||||||
|
|
||||||
|
Check which migrations exist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la db/migrations/
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply them in order:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all migrations sequentially
|
||||||
|
for f in db/migrations/*.sql; do
|
||||||
|
echo "Applying $f ..."
|
||||||
|
docker compose exec -T postgres psql \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
< "$f"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Or apply a specific migration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres psql \
|
||||||
|
-U hoafinance \
|
||||||
|
-d hoafinance \
|
||||||
|
< db/migrations/010-health-scores.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Migrations are idempotent where possible (`IF NOT EXISTS`,
|
||||||
|
> `DO $$ ... $$` blocks), so re-running one that has already been applied
|
||||||
|
> is generally safe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifying the Deployment
|
||||||
|
|
||||||
|
### Quick health checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend is responding
|
||||||
|
curl -s http://localhost/api/auth/login | head -c 100
|
||||||
|
|
||||||
|
# Database is accessible
|
||||||
|
docker compose exec -T postgres psql -U hoafinance -d hoafinance \
|
||||||
|
-c "SELECT count(*) AS tenants FROM shared.organizations WHERE status = 'active';"
|
||||||
|
|
||||||
|
# Redis is working
|
||||||
|
docker compose exec -T redis redis-cli ping
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full smoke test
|
||||||
|
|
||||||
|
1. Open `http://<server-ip>` in a browser
|
||||||
|
2. Log in with a known account
|
||||||
|
3. Navigate to Dashboard — verify health scores load
|
||||||
|
4. Navigate to Capital Planning — verify Kanban columns render
|
||||||
|
5. Navigate to Projects — verify project list loads
|
||||||
|
6. Check the Settings page — version should read **2026.3.2 (beta)**
|
||||||
|
|
||||||
|
### View logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs -f # all services
|
||||||
|
docker compose logs -f backend # backend only
|
||||||
|
docker compose logs -f postgres # database only
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variable Reference
|
||||||
|
|
||||||
|
| Variable | Required | Description |
|
||||||
|
|-------------------|----------|----------------------------------------------------|
|
||||||
|
| `POSTGRES_USER` | Yes | PostgreSQL username |
|
||||||
|
| `POSTGRES_PASSWORD`| Yes | PostgreSQL password (**change from default**) |
|
||||||
|
| `POSTGRES_DB` | Yes | Database name |
|
||||||
|
| `DATABASE_URL` | Yes | Full connection string for the backend |
|
||||||
|
| `REDIS_URL` | Yes | Redis connection string |
|
||||||
|
| `JWT_SECRET` | Yes | Secret for signing JWT tokens (**change from default**) |
|
||||||
|
| `NODE_ENV` | Yes | `development` or `production` |
|
||||||
|
| `AI_API_URL` | Yes | OpenAI-compatible inference endpoint |
|
||||||
|
| `AI_API_KEY` | Yes | API key for AI provider (Nvidia) |
|
||||||
|
| `AI_MODEL` | Yes | Model identifier for AI calls |
|
||||||
|
| `AI_DEBUG` | No | Set `true` to log raw AI prompts/responses |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐
|
||||||
|
Browser ────────► │ nginx :80 │
|
||||||
|
└──────┬──────┘
|
||||||
|
┌────────┴────────┐
|
||||||
|
▼ ▼
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ backend :3000│ │frontend :5173│
|
||||||
|
│ (NestJS) │ │ (Vite/React) │
|
||||||
|
└──────┬───────┘ └──────────────┘
|
||||||
|
┌────┴────┐
|
||||||
|
▼ ▼
|
||||||
|
┌────────────┐ ┌───────────┐
|
||||||
|
│postgres:5432│ │redis :6379│
|
||||||
|
│ (PG 15) │ │ (Redis 7) │
|
||||||
|
└────────────┘ └───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Multi-tenant isolation:** Each HOA organization gets its own PostgreSQL
|
||||||
|
schema (e.g., `tenant_pine_creek_hoa_q33i`). The `shared` schema holds
|
||||||
|
cross-tenant tables (users, organizations, market rates). Tenant context
|
||||||
|
is resolved from the JWT token on every API request.
|
||||||
Reference in New Issue
Block a user