doc: prod deploy

This commit is contained in:
Oleg Proskurin 2025-12-03 00:57:11 +07:00
parent 56c6ba536e
commit 5d28f6e6ee
3 changed files with 292 additions and 55 deletions

234
docs/deployment-plan.md Normal file
View File

@ -0,0 +1,234 @@
# Banatie Production Deployment Plan
## Architecture
### Isolated Banatie Stack (5 containers):
```
banatie-landing - Landing Page (node:20-alpine)
banatie-api - API Service (node:20-alpine)
banatie-postgres - PostgreSQL 15 (isolated from VPS PostgreSQL)
banatie-minio - MinIO storage (SNMD mode)
banatie-minio-init - Initialization (one-shot)
```
### Domains:
```
banatie.app → banatie-landing:3000
api.banatie.app → banatie-api:3000
storage.banatie.app → banatie-minio:9001 (MinIO Console)
```
### File Structure:
```
~/workspace/projects/banatie-service/ # Git repository (code + config)
├── apps/
│ ├── api-service/Dockerfile
│ └── landing/Dockerfile
├── packages/database/
└── prod-env/
├── docker-compose.yml # Production compose (run from here!)
├── .env # Config without secrets (in git)
└── secrets.env # Secrets (NOT in git)
/opt/services/data/banatie/ # Runtime data only
├── postgres/ # PostgreSQL data
├── storage/drive{1-4}/ # MinIO SNMD
├── logs/ # API logs
├── results/ # Generated images
└── uploads/ # Uploaded files
```
---
## Docker Images
| Container | Image | Internal Port |
|-----------|-------|---------------|
| banatie-landing | Built from apps/landing/Dockerfile | 3000 |
| banatie-api | Built from apps/api-service/Dockerfile | 3000 |
| banatie-postgres | postgres:15-alpine | 5432 |
| banatie-minio | quay.io/minio/minio:latest | 9000 (API), 9001 (Console) |
---
## Deployment Steps
### Step 1: Prepare VPS Directories
```bash
# Create runtime data directory
sudo mkdir -p /opt/services/data/banatie/{postgres,logs,results,uploads}
sudo mkdir -p /opt/services/data/banatie/storage/drive{1,2,3,4}
sudo chown -R $USER:$USER /opt/services/data/banatie
```
### Step 2: Configure Repository
```bash
cd ~/workspace/projects/banatie-service
# Create secrets.env (not in git)
cp prod-env/secrets.env.example prod-env/secrets.env
nano prod-env/secrets.env
# Fill in: GEMINI_API_KEY, etc.
```
### Step 3: Configure Caddy
Add to `/opt/services/configs/caddy/Caddyfile`:
```caddy
banatie.app, www.banatie.app {
reverse_proxy banatie-landing:3000
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
}
}
api.banatie.app {
reverse_proxy banatie-api:3000
encode gzip
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
}
}
storage.banatie.app {
reverse_proxy banatie-minio:9001
header {
Strict-Transport-Security "max-age=31536000"
}
}
```
### Step 4: First Launch
```bash
cd ~/workspace/projects/banatie-service/prod-env
# Start stack
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f
# Restart Caddy to apply config
docker restart caddy
# Create master key
curl -X POST https://api.banatie.app/api/bootstrap/initial-key
```
---
## Update Process
### Update Landing:
```bash
cd ~/workspace/projects/banatie-service
git pull origin main
cd prod-env
docker compose up -d --build banatie-landing
```
### Update API:
```bash
cd ~/workspace/projects/banatie-service
git pull origin main
cd prod-env
docker compose up -d --build banatie-api
```
### Update Both:
```bash
git pull origin main
cd prod-env
docker compose up -d --build banatie-landing banatie-api
```
### Full Restart:
```bash
cd ~/workspace/projects/banatie-service/prod-env
docker compose down
docker compose up -d --build
```
**Downtime:** ~2-3 minutes for image build
---
## DNS Records (add in GoDaddy)
| Record | Type | Value |
|--------|------|-------|
| banatie.app | A | 62.146.239.118 |
| www | CNAME | banatie.app |
| api | A | 62.146.239.118 |
| storage | A | 62.146.239.118 |
---
## Migration from Static Landing
After successful Docker deployment:
1. Remove `/var/www/banatie.app/` (old static files)
2. Replace banatie.app section in Caddyfile (static → reverse_proxy)
3. `docker restart caddy`
---
## Network Architecture
### Internal Network (banatie-internal)
- All containers communicate internally
- No external port exposure
- PostgreSQL and MinIO API only accessible internally
### External Network (proxy-network)
- Shared with Caddy reverse proxy
- Landing, API, and MinIO Console exposed to Caddy
- SSL termination at Caddy
---
## Security Notes
1. **No exposed ports** - All traffic goes through Caddy with SSL
2. **Isolated PostgreSQL** - Separate from VPS main database
3. **Secrets management** - `secrets.env` not tracked in git
4. **MinIO SNMD mode** - Erasure coding for data protection
---
## Troubleshooting
### Check container status:
```bash
cd ~/workspace/projects/banatie-service/prod-env
docker compose ps
docker compose logs banatie-api
docker compose logs banatie-landing
```
### Restart specific service:
```bash
docker compose restart banatie-api
```
### Full rebuild:
```bash
docker compose down
docker compose build --no-cache
docker compose up -d
```
### Check Caddy logs:
```bash
docker logs caddy
```

View File

@ -5,29 +5,29 @@
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
API_BASE_URL=http://localhost:3000
API_BASE_URL=https://api.banatie.app
# CORS Configuration
CORS_ORIGIN=*
# Database Configuration (Docker internal network)
DB_HOST=postgres
DB_HOST=banatie-postgres
DB_PORT=5432
DB_NAME=banatie_db
DB_USER=banatie_user
DB_PASSWORD=banatie_secure_password
DATABASE_URL=postgresql://banatie_user:banatie_secure_password@postgres:5432/banatie_db
DATABASE_URL=postgresql://banatie_user:banatie_secure_password@banatie-postgres:5432/banatie_db
# MinIO Storage Configuration (Docker internal network)
MINIO_ROOT_USER=banatie_admin
MINIO_ROOT_PASSWORD=banatie_storage_secure_key_2024
STORAGE_TYPE=minio
MINIO_ENDPOINT=minio:9000
MINIO_ENDPOINT=banatie-minio:9000
MINIO_ACCESS_KEY=banatie_service
MINIO_SECRET_KEY=banatie_service_key_2024
MINIO_USE_SSL=false
MINIO_BUCKET_NAME=banatie
MINIO_PUBLIC_URL=http://localhost:9000
MINIO_PUBLIC_URL=https://storage.banatie.app
# Multi-tenancy Configuration
DEFAULT_ORG_ID=default

View File

@ -1,23 +1,27 @@
# Banatie Production Docker Compose
# Run from: ~/workspace/projects/banatie-service/prod-env/
# Data stored in: /opt/services/data/banatie/
services:
# API Service
app:
banatie-api:
build:
context: ..
dockerfile: apps/api-service/Dockerfile
target: production
container_name: banatie-app
ports:
- "3000:3000"
container_name: banatie-api
# No ports exposed - access through Caddy reverse proxy
volumes:
- ../apps/api-service/logs:/app/apps/api-service/logs
- ../data/results:/app/results
- ../data/uploads:/app/uploads
- /opt/services/data/banatie/logs:/app/apps/api-service/logs
- /opt/services/data/banatie/results:/app/results
- /opt/services/data/banatie/uploads:/app/uploads
networks:
- banatie-network
- banatie-internal
- proxy-network
depends_on:
postgres:
banatie-postgres:
condition: service_healthy
minio:
banatie-minio:
condition: service_healthy
env_file:
- .env
@ -28,17 +32,17 @@ services:
restart: unless-stopped
# Landing Page
landing:
banatie-landing:
build:
context: ..
dockerfile: apps/landing/Dockerfile
container_name: banatie-landing
ports:
- "3001:3000"
# No ports exposed - access through Caddy reverse proxy
networks:
- banatie-network
- banatie-internal
- proxy-network
depends_on:
- postgres
- banatie-postgres
env_file:
- .env
- secrets.env
@ -47,21 +51,20 @@ services:
- NODE_ENV=production
restart: unless-stopped
# PostgreSQL Database
postgres:
# PostgreSQL Database (isolated for Banatie)
banatie-postgres:
image: postgres:15-alpine
container_name: banatie-postgres
ports:
- "5460:5432"
# No ports exposed - internal access only
volumes:
- ../data/postgres:/var/lib/postgresql/data
- /opt/services/data/banatie/postgres:/var/lib/postgresql/data
- ../scripts/init-db.sql:/docker-entrypoint-initdb.d/01-init.sql
networks:
- banatie-network
- banatie-internal
environment:
POSTGRES_DB: banatie_db
POSTGRES_USER: banatie_user
POSTGRES_PASSWORD: banatie_secure_password
POSTGRES_PASSWORD: ${DB_PASSWORD:-banatie_secure_password}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U banatie_user -d banatie_db"]
interval: 30s
@ -71,26 +74,25 @@ services:
restart: unless-stopped
# MinIO Object Storage - Production Ready with SNMD
minio:
banatie-minio:
image: quay.io/minio/minio:latest
container_name: banatie-storage
ports:
- "9000:9000" # S3 API
- "9001:9001" # Web Console
container_name: banatie-minio
# No ports exposed - Console through Caddy, API internal only
volumes:
# SNMD: 4 drives for full S3 compatibility and erasure coding
- ../data/storage/drive1:/data1
- ../data/storage/drive2:/data2
- ../data/storage/drive3:/data3
- ../data/storage/drive4:/data4
- /opt/services/data/banatie/storage/drive1:/data1
- /opt/services/data/banatie/storage/drive2:/data2
- /opt/services/data/banatie/storage/drive3:/data3
- /opt/services/data/banatie/storage/drive4:/data4
networks:
- banatie-network
- banatie-internal
- proxy-network # For MinIO Console access via Caddy
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_BROWSER_REDIRECT_URL: http://localhost:9001
MINIO_SERVER_URL: http://localhost:9000
MINIO_DOMAIN: localhost
# Production URLs (through Caddy)
MINIO_BROWSER_REDIRECT_URL: https://storage.banatie.app
MINIO_SERVER_URL: https://api.banatie.app
# CRITICAL: SNMD command for full S3 compatibility
command: server /data{1...4} --console-address ":9001"
healthcheck:
@ -102,29 +104,32 @@ services:
restart: unless-stopped
# MinIO Storage Initialization
storage-init:
banatie-minio-init:
image: minio/mc:latest
container_name: banatie-storage-init
container_name: banatie-minio-init
networks:
- banatie-network
- banatie-internal
depends_on:
minio:
banatie-minio:
condition: service_healthy
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
entrypoint:
- /bin/sh
- -c
- |
echo 'Setting up MinIO alias...'
mc alias set storage http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}
mc alias set storage http://banatie-minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}
echo 'Creating main bucket...'
mc mb --ignore-existing storage/banatie
echo 'Creating service user...'
mc admin user add storage banatie_service banatie_service_key_2024
mc admin user add storage banatie_service banatie_service_key_2024 || true
echo 'Attaching readwrite policy to service user...'
mc admin policy attach storage readwrite --user=banatie_service
mc admin policy attach storage readwrite --user=banatie_service || true
echo 'Setting up lifecycle policy...'
cat > /tmp/lifecycle.json <<'LIFECYCLE'
@ -143,7 +148,7 @@ services:
]
}
LIFECYCLE
mc ilm import storage/banatie < /tmp/lifecycle.json
mc ilm import storage/banatie < /tmp/lifecycle.json || true
echo 'Storage initialization completed!'
echo 'Bucket: banatie'
@ -153,11 +158,9 @@ services:
restart: "no"
networks:
banatie-network:
# Internal network for service-to-service communication
banatie-internal:
driver: bridge
volumes:
postgres-data:
driver: local
storage-data:
driver: local
# External network shared with Caddy reverse proxy
proxy-network:
external: true