Add production infrastructure: compiled builds, clustering, connection pooling
Root cause of 502 errors under 30 concurrent users: the production server was running dev-mode infrastructure (Vite dev server, NestJS --watch, no DB connection pooling, single Node.js process). Changes: - backend/Dockerfile: multi-stage prod build (compiled JS, no devDeps) - frontend/Dockerfile: multi-stage prod build (static assets served by nginx) - frontend/nginx.conf: SPA routing config for frontend container - docker-compose.prod.yml: production overlay with tuned Postgres, memory limits, health checks, restart policies - nginx/production.conf: keepalive upstreams, proxy buffering, rate limiting - backend/src/main.ts: Node.js clustering (1 worker per CPU, up to 4), conditional request logging, production CORS - backend/src/app.module.ts: TypeORM connection pool (max 30, min 5) - docs/DEPLOYMENT.md: new Production Deployment section Deploy with: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,12 +9,13 @@
|
||||
|
||||
1. [Prerequisites](#prerequisites)
|
||||
2. [Deploy to a Fresh Docker Server](#deploy-to-a-fresh-docker-server)
|
||||
3. [SSL with Certbot (Let's Encrypt)](#ssl-with-certbot-lets-encrypt)
|
||||
4. [Backup the Local Test Database](#backup-the-local-test-database)
|
||||
5. [Restore a Backup into the Staged Environment](#restore-a-backup-into-the-staged-environment)
|
||||
6. [Running Migrations on the Staged Environment](#running-migrations-on-the-staged-environment)
|
||||
7. [Verifying the Deployment](#verifying-the-deployment)
|
||||
8. [Environment Variable Reference](#environment-variable-reference)
|
||||
3. [Production Deployment](#production-deployment)
|
||||
4. [SSL with Certbot (Let's Encrypt)](#ssl-with-certbot-lets-encrypt)
|
||||
5. [Backup the Local Test Database](#backup-the-local-test-database)
|
||||
6. [Restore a Backup into the Staged Environment](#restore-a-backup-into-the-staged-environment)
|
||||
7. [Running Migrations on the Staged Environment](#running-migrations-on-the-staged-environment)
|
||||
8. [Verifying the Deployment](#verifying-the-deployment)
|
||||
9. [Environment Variable Reference](#environment-variable-reference)
|
||||
|
||||
---
|
||||
|
||||
@@ -135,8 +136,95 @@ This creates:
|
||||
| API | `http://<server-ip>/api` |
|
||||
| Postgres | `<server-ip>:5432` (direct) |
|
||||
|
||||
> At this point the app is running over **plain HTTP**. Continue to the next
|
||||
> section to enable HTTPS.
|
||||
> At this point the app is running over **plain HTTP** in development mode.
|
||||
> For any environment that will serve real traffic, continue to the Production
|
||||
> Deployment section.
|
||||
|
||||
---
|
||||
|
||||
## Production Deployment
|
||||
|
||||
The base `docker-compose.yml` runs everything in **development mode** (Vite
|
||||
dev server, NestJS in watch mode, no connection pooling). This is fine for
|
||||
local development but will fail under even light production load.
|
||||
|
||||
`docker-compose.prod.yml` provides a production overlay that fixes this:
|
||||
|
||||
| Component | Dev mode | Production mode |
|
||||
|-----------|----------|-----------------|
|
||||
| Frontend | Vite dev server (single-threaded, HMR) | Static build served by nginx |
|
||||
| Backend | `nest start --watch` (ts-node, file watcher) | Compiled JS, clustered across CPU cores |
|
||||
| DB pooling | None (new connection per query) | Pool of 30 reusable connections |
|
||||
| Postgres | Default config (100 connections) | Tuned: 200 connections, optimized buffers |
|
||||
| Nginx | Basic proxy | Keepalive upstreams, buffering, rate limiting |
|
||||
| Restart | None | `unless-stopped` on all services |
|
||||
|
||||
### Deploy for production
|
||||
|
||||
```bash
|
||||
cd /opt/hoa-ledgeriq
|
||||
|
||||
# Ensure .env has NODE_ENV=production and strong secrets
|
||||
nano .env
|
||||
|
||||
# Build and start with the production overlay
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
To add SSL on top of the production stack:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f docker-compose.yml \
|
||||
-f docker-compose.prod.yml \
|
||||
-f docker-compose.ssl.yml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
> **Tip:** Create a shell alias to avoid typing the compose files every time:
|
||||
> ```bash
|
||||
> echo 'alias dc="docker compose -f docker-compose.yml -f docker-compose.prod.yml"' >> ~/.bashrc
|
||||
> source ~/.bashrc
|
||||
> dc up -d --build
|
||||
> ```
|
||||
|
||||
### What the production overlay does
|
||||
|
||||
**Backend (`backend/Dockerfile`)**
|
||||
- Multi-stage build: compiles TypeScript once, runs `node dist/main`
|
||||
- No dev dependencies shipped (smaller image, faster startup)
|
||||
- Node.js clustering: forks one worker per CPU core (up to 4)
|
||||
- Connection pool: 30 reusable PostgreSQL connections shared across workers
|
||||
|
||||
**Frontend (`frontend/Dockerfile`)**
|
||||
- Multi-stage build: `npm run build` produces optimized static assets
|
||||
- Served by a lightweight nginx container (not Vite)
|
||||
- Static assets cached with immutable headers (Vite filename hashing)
|
||||
|
||||
**Nginx (`nginx/production.conf`)**
|
||||
- Keepalive connections to upstream services (connection reuse)
|
||||
- Proxy buffering to prevent 502s during slow responses
|
||||
- Rate limiting on API routes (10 req/s per IP, burst 30)
|
||||
- Proper timeouts tuned per endpoint type
|
||||
|
||||
**PostgreSQL**
|
||||
- `max_connections=200` (up from default 100)
|
||||
- `shared_buffers=256MB`, `effective_cache_size=512MB`
|
||||
- Tuned checkpoint, WAL, and memory settings
|
||||
|
||||
### Capacity guidelines
|
||||
|
||||
With the production stack on a 2-core / 4GB server:
|
||||
|
||||
| Metric | Expected capacity |
|
||||
|--------|-------------------|
|
||||
| Concurrent users | 50–100 |
|
||||
| API requests/sec | ~200 |
|
||||
| DB connections | 30 per backend worker × workers |
|
||||
| Frontend serving | Static files, effectively unlimited |
|
||||
|
||||
For higher loads, scale the backend horizontally with Docker Swarm or
|
||||
Kubernetes replicas.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user