docs: deploying instruction
This commit is contained in:
parent
77006e8f47
commit
6defdbafc4
|
|
@ -0,0 +1,947 @@
|
||||||
|
# Banatie Service Infrastructure Documentation v1.0
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document defines the complete containerization and deployment architecture for the Banatie AI image generation service on the usul.su VPS infrastructure.
|
||||||
|
|
||||||
|
**Target VPS:** 62.146.239.118 (Contabo Singapore)
|
||||||
|
**Environment:** Ubuntu 24.04.2 LTS with Docker User Namespace Remapping
|
||||||
|
|
||||||
|
## Architecture Summary
|
||||||
|
|
||||||
|
### Core Principles
|
||||||
|
|
||||||
|
- **Complete Service Isolation**: Banatie ecosystem is fully isolated from core VPS services (NextCloud, Gitea)
|
||||||
|
- **Dedicated Database**: Separate PostgreSQL container exclusively for Banatie
|
||||||
|
- **S3-Compatible Storage**: MinIO SNMD (Single Node Multi Drive) for full S3 compatibility
|
||||||
|
- **Container-First Approach**: All components run as Docker containers
|
||||||
|
- **Network Segregation**: Internal communication via dedicated Docker networks
|
||||||
|
- **No Port Exposure**: All external access via Caddy reverse proxy only
|
||||||
|
|
||||||
|
### Service Components
|
||||||
|
|
||||||
|
```
|
||||||
|
Banatie Ecosystem (Isolated from /opt/services/)
|
||||||
|
├── Banatie API (Express.js/TypeScript) → api.banatie.app
|
||||||
|
├── Banatie Landing (Next.js standalone) → banatie.app
|
||||||
|
├── PostgreSQL 15 (Dedicated instance)
|
||||||
|
├── MinIO SNMD (S3-compatible storage)
|
||||||
|
│ ├── Console → storage.banatie.app
|
||||||
|
│ └── S3 API → cdn.banatie.app
|
||||||
|
└── Storage Init (one-time bucket setup)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domain Architecture
|
||||||
|
|
||||||
|
| Domain | Service | Container | Port |
|
||||||
|
|--------|---------|-----------|------|
|
||||||
|
| `banatie.app` | Landing Page | banatie-landing | 3000 |
|
||||||
|
| `api.banatie.app` | REST API | banatie-api | 3000 |
|
||||||
|
| `storage.banatie.app` | MinIO Console | banatie-minio | 9001 |
|
||||||
|
| `cdn.banatie.app` | MinIO S3 (images) | banatie-minio | 9000 |
|
||||||
|
|
||||||
|
## Network Architecture
|
||||||
|
|
||||||
|
### Production Networks
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
banatie-internal:
|
||||||
|
driver: bridge
|
||||||
|
internal: true # 🔒 CRITICAL: No external internet access
|
||||||
|
|
||||||
|
proxy-network:
|
||||||
|
external: true # Existing Caddy reverse proxy network
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Access Matrix
|
||||||
|
|
||||||
|
| Service | banatie-internal | proxy-network | External Access |
|
||||||
|
|---------|-----------------|---------------|-----------------|
|
||||||
|
| banatie-api | ✅ Internal | ✅ HTTP only | ❌ Direct |
|
||||||
|
| banatie-landing | ✅ Internal | ✅ HTTP only | ❌ Direct |
|
||||||
|
| banatie-postgres | ✅ Internal | ❌ None | ❌ None |
|
||||||
|
| banatie-minio | ✅ Internal | ✅ Console+S3 | ❌ Direct |
|
||||||
|
| Caddy (external) | ❌ None | ✅ Routing | ✅ Internet |
|
||||||
|
|
||||||
|
### VPS Integration Points
|
||||||
|
|
||||||
|
**Isolation from Core Services:**
|
||||||
|
- **NO** shared resources with `/opt/services/` infrastructure
|
||||||
|
- **NO** access to NextCloud, Gitea, or core PostgreSQL
|
||||||
|
- **ONLY** connection point: Caddy reverse proxy (proxy-network)
|
||||||
|
|
||||||
|
**Shared Infrastructure:**
|
||||||
|
- Caddy reverse proxy for SSL termination and routing
|
||||||
|
- Host filesystem for persistent data storage
|
||||||
|
- UFW firewall rules and Docker User Namespace Remapping
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
### Production Environment (VPS)
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/banatie/ # Isolated Banatie deployment
|
||||||
|
├── docker-compose.yml # Production configuration
|
||||||
|
├── .env # Public environment variables
|
||||||
|
├── secrets.env # Sensitive secrets (chmod 600)
|
||||||
|
├── scripts/
|
||||||
|
│ └── init-db.sql # PostgreSQL 15 permission fix (minimal)
|
||||||
|
├── data/ # Persistent data (Docker-managed!)
|
||||||
|
│ ├── postgres/ # PostgreSQL data
|
||||||
|
│ ├── minio/ # MinIO SNMD storage
|
||||||
|
│ │ ├── drive1/
|
||||||
|
│ │ ├── drive2/
|
||||||
|
│ │ ├── drive3/
|
||||||
|
│ │ └── drive4/
|
||||||
|
│ └── waitlist-logs/ # Landing waitlist emails
|
||||||
|
└── logs/
|
||||||
|
├── api/ # API service logs
|
||||||
|
└── landing/ # Landing app logs
|
||||||
|
|
||||||
|
# Source code (separate from production)
|
||||||
|
~/workspace/projects/banatie-service/
|
||||||
|
├── apps/
|
||||||
|
│ ├── api-service/ # Express.js API source
|
||||||
|
│ │ └── Dockerfile # Production build
|
||||||
|
│ └── landing/ # Next.js landing source
|
||||||
|
│ └── Dockerfile # Production build
|
||||||
|
├── packages/
|
||||||
|
│ └── database/ # Drizzle ORM schema (source of truth)
|
||||||
|
│ ├── src/schema/ # Actual database schema
|
||||||
|
│ └── migrations/ # Drizzle migrations
|
||||||
|
└── prod-env/ # Local testing (NOT for VPS)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code Repository
|
||||||
|
|
||||||
|
Source code is managed in `~/workspace/projects/banatie-service/` and cloned from Gitea:
|
||||||
|
```bash
|
||||||
|
git clone ssh://git@git.usul.su:2222/usulpro/banatie-service.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Production configuration lives in `/opt/banatie/` (separate from source).
|
||||||
|
|
||||||
|
## Docker User Namespace Remapping
|
||||||
|
|
||||||
|
**Status:** ✅ ENABLED on VPS for enhanced container security
|
||||||
|
|
||||||
|
**Configuration:** `/etc/docker/daemon.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"userns-remap": "default"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What This Means:**
|
||||||
|
- Container UIDs are mapped to high-numbered host UIDs (base offset: 165536)
|
||||||
|
- PostgreSQL UID 70 inside container → UID 165606 on host
|
||||||
|
- MinIO UID 1000 inside container → UID 166536 on host
|
||||||
|
- Enhanced security: containers cannot access host files with original UIDs
|
||||||
|
|
||||||
|
### Critical Rules
|
||||||
|
|
||||||
|
- ❌ **DO NOT** manually create directories in `/opt/banatie/data/`
|
||||||
|
- ❌ **DO NOT** manually chown data directories
|
||||||
|
- ✅ **LET DOCKER** handle directory creation and permissions
|
||||||
|
- ✅ Use `docker exec` for container file operations
|
||||||
|
|
||||||
|
### Troubleshooting Permission Issues
|
||||||
|
|
||||||
|
If a service fails with permission errors after VPS reboot:
|
||||||
|
```bash
|
||||||
|
# Check container's internal UID
|
||||||
|
docker exec banatie-postgres id
|
||||||
|
# Expected output: uid=70(postgres)
|
||||||
|
|
||||||
|
# Calculate host UID: 70 + 165536 = 165606
|
||||||
|
# Fix permissions if needed
|
||||||
|
sudo chown -R 165606:165606 /opt/banatie/data/postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container Specifications
|
||||||
|
|
||||||
|
### 1. Banatie API Container
|
||||||
|
|
||||||
|
**Base Image**: `node:20-alpine`
|
||||||
|
**Build Strategy**: Multi-stage for optimization
|
||||||
|
**Source**: `apps/api-service/Dockerfile`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Non-root user (apiuser:nodejs, UID 1001)
|
||||||
|
- Health check endpoint at `/health`
|
||||||
|
- Structured logging to `/app/apps/api-service/logs`
|
||||||
|
- Database connection via Drizzle ORM
|
||||||
|
|
||||||
|
### 2. Banatie Landing Container
|
||||||
|
|
||||||
|
**Base Image**: `node:20-alpine`
|
||||||
|
**Build Strategy**: Multi-stage Next.js standalone
|
||||||
|
**Source**: `apps/landing/Dockerfile`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Non-root user (nextjs:nodejs, UID 1001)
|
||||||
|
- Health check at root path
|
||||||
|
- Waitlist email logging to `/app/waitlist-logs`
|
||||||
|
|
||||||
|
### 3. PostgreSQL Container
|
||||||
|
|
||||||
|
**Image**: `postgres:15-alpine`
|
||||||
|
**Purpose**: Dedicated database for Banatie services
|
||||||
|
**Internal UID**: 70 (postgres user)
|
||||||
|
|
||||||
|
**Database Schema** (managed by Drizzle ORM):
|
||||||
|
- `organizations` - Multi-tenant organization data
|
||||||
|
- `projects` - Projects within organizations
|
||||||
|
- `api_keys` - API key authentication
|
||||||
|
- `flows` - Generation workflow definitions
|
||||||
|
- `images` - Image metadata and references
|
||||||
|
- `generations` - Generation history
|
||||||
|
- `prompt_url_cache` - URL caching for prompts
|
||||||
|
- `live_scopes` - Live scope definitions
|
||||||
|
|
||||||
|
**Note**: Schema is defined in `packages/database/src/schema/` and managed via Drizzle. The `scripts/init-db.sql` contains only PostgreSQL 15 permission fixes.
|
||||||
|
|
||||||
|
### 4. MinIO Container
|
||||||
|
|
||||||
|
**Image**: `quay.io/minio/minio:latest`
|
||||||
|
**Purpose**: S3-compatible object storage
|
||||||
|
**Mode**: SNMD (Single Node Multi Drive) for full S3 compatibility and erasure coding
|
||||||
|
|
||||||
|
**Storage Configuration:**
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- /opt/banatie/data/minio/drive1:/data1
|
||||||
|
- /opt/banatie/data/minio/drive2:/data2
|
||||||
|
- /opt/banatie/data/minio/drive3:/data3
|
||||||
|
- /opt/banatie/data/minio/drive4:/data4
|
||||||
|
command: server /data{1...4} --console-address ":9001"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why SNMD:**
|
||||||
|
- Full S3 API compatibility
|
||||||
|
- Erasure coding for data durability
|
||||||
|
- Required for Banatie startup project requirements
|
||||||
|
|
||||||
|
### 5. Storage Init Container
|
||||||
|
|
||||||
|
**Image**: `minio/mc:latest`
|
||||||
|
**Purpose**: One-time MinIO initialization
|
||||||
|
**Restart Policy**: `no` (runs once)
|
||||||
|
|
||||||
|
**Initialization Tasks:**
|
||||||
|
1. Create main bucket (`banatie`)
|
||||||
|
2. Create service account for application access
|
||||||
|
3. Attach readwrite policy to service account
|
||||||
|
4. Configure lifecycle policy for temp file cleanup (7 days)
|
||||||
|
|
||||||
|
## Production Docker Compose
|
||||||
|
|
||||||
|
**File**: `/opt/banatie/docker-compose.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Banatie Production - VPS Isolated Deployment
|
||||||
|
# Last Updated: December 2025
|
||||||
|
|
||||||
|
services:
|
||||||
|
# ===========================================
|
||||||
|
# API Service (Express.js)
|
||||||
|
# ===========================================
|
||||||
|
banatie-api:
|
||||||
|
build:
|
||||||
|
context: /home/usul/workspace/projects/banatie-service
|
||||||
|
dockerfile: apps/api-service/Dockerfile
|
||||||
|
target: production
|
||||||
|
container_name: banatie-api
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- banatie-internal
|
||||||
|
- proxy-network
|
||||||
|
depends_on:
|
||||||
|
banatie-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
banatie-minio:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file:
|
||||||
|
- /opt/banatie/.env
|
||||||
|
- /opt/banatie/secrets.env
|
||||||
|
environment:
|
||||||
|
- IS_DOCKER=true
|
||||||
|
- NODE_ENV=production
|
||||||
|
volumes:
|
||||||
|
- /opt/banatie/logs/api:/app/apps/api-service/logs
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Landing Page (Next.js standalone)
|
||||||
|
# ===========================================
|
||||||
|
banatie-landing:
|
||||||
|
build:
|
||||||
|
context: /home/usul/workspace/projects/banatie-service
|
||||||
|
dockerfile: apps/landing/Dockerfile
|
||||||
|
container_name: banatie-landing
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- banatie-internal
|
||||||
|
- proxy-network
|
||||||
|
depends_on:
|
||||||
|
- banatie-postgres
|
||||||
|
env_file:
|
||||||
|
- /opt/banatie/.env
|
||||||
|
- /opt/banatie/secrets.env
|
||||||
|
environment:
|
||||||
|
- IS_DOCKER=true
|
||||||
|
- NODE_ENV=production
|
||||||
|
- WAITLIST_LOGS_PATH=/app/waitlist-logs
|
||||||
|
volumes:
|
||||||
|
- /opt/banatie/data/waitlist-logs:/app/waitlist-logs
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# PostgreSQL (Dedicated - ISOLATED)
|
||||||
|
# ===========================================
|
||||||
|
banatie-postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: banatie-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- banatie-internal
|
||||||
|
# NO proxy-network - complete isolation from external access
|
||||||
|
volumes:
|
||||||
|
- /opt/banatie/data/postgres:/var/lib/postgresql/data
|
||||||
|
- /opt/banatie/scripts/init-db.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# MinIO Object Storage (SNMD - 4 drives)
|
||||||
|
# ===========================================
|
||||||
|
banatie-minio:
|
||||||
|
image: quay.io/minio/minio:latest
|
||||||
|
container_name: banatie-minio
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- banatie-internal
|
||||||
|
- proxy-network
|
||||||
|
volumes:
|
||||||
|
- /opt/banatie/data/minio/drive1:/data1
|
||||||
|
- /opt/banatie/data/minio/drive2:/data2
|
||||||
|
- /opt/banatie/data/minio/drive3:/data3
|
||||||
|
- /opt/banatie/data/minio/drive4:/data4
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
|
MINIO_BROWSER_REDIRECT_URL: https://storage.banatie.app
|
||||||
|
MINIO_SERVER_URL: https://cdn.banatie.app
|
||||||
|
command: server /data{1...4} --console-address ":9001"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# MinIO Initialization (one-time)
|
||||||
|
# ===========================================
|
||||||
|
banatie-storage-init:
|
||||||
|
image: minio/mc:latest
|
||||||
|
container_name: banatie-storage-init
|
||||||
|
networks:
|
||||||
|
- banatie-internal
|
||||||
|
depends_on:
|
||||||
|
banatie-minio:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file:
|
||||||
|
- /opt/banatie/secrets.env
|
||||||
|
entrypoint:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
echo '=== MinIO Storage Initialization ==='
|
||||||
|
|
||||||
|
echo 'Setting up MinIO alias...'
|
||||||
|
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 account...'
|
||||||
|
mc admin user add storage $${MINIO_ACCESS_KEY} $${MINIO_SECRET_KEY} || echo 'User may already exist'
|
||||||
|
|
||||||
|
echo 'Attaching readwrite policy...'
|
||||||
|
mc admin policy attach storage readwrite --user=$${MINIO_ACCESS_KEY} || echo 'Policy may already be attached'
|
||||||
|
|
||||||
|
echo 'Setting up lifecycle policy for temp cleanup...'
|
||||||
|
cat > /tmp/lifecycle.json <<'EOF'
|
||||||
|
{
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"ID": "temp-cleanup",
|
||||||
|
"Status": "Enabled",
|
||||||
|
"Filter": {"Prefix": "temp/"},
|
||||||
|
"Expiration": {"Days": 7}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
mc ilm import storage/banatie < /tmp/lifecycle.json || echo 'Lifecycle policy may already exist'
|
||||||
|
|
||||||
|
echo '=== Storage Initialization Completed ==='
|
||||||
|
echo 'Bucket: banatie'
|
||||||
|
echo 'Service User: configured'
|
||||||
|
echo 'Lifecycle: 7-day temp cleanup'
|
||||||
|
exit 0
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Networks
|
||||||
|
# ===========================================
|
||||||
|
networks:
|
||||||
|
banatie-internal:
|
||||||
|
driver: bridge
|
||||||
|
internal: true # 🔒 CRITICAL: No external internet access
|
||||||
|
|
||||||
|
proxy-network:
|
||||||
|
name: proxy-network
|
||||||
|
external: true # Existing Caddy network from /opt/services/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Configuration
|
||||||
|
|
||||||
|
### Public Environment (`/opt/banatie/.env`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ===========================================
|
||||||
|
# Banatie Production Environment
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
# Application
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Database (internal Docker hostname)
|
||||||
|
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@banatie-postgres:5432/${POSTGRES_DB}
|
||||||
|
POSTGRES_DB=banatie_db
|
||||||
|
POSTGRES_USER=banatie_user
|
||||||
|
|
||||||
|
# MinIO (internal Docker hostname)
|
||||||
|
MINIO_ENDPOINT=banatie-minio:9000
|
||||||
|
MINIO_BUCKET_NAME=banatie
|
||||||
|
MINIO_USE_SSL=false
|
||||||
|
|
||||||
|
# Public URLs (for generated links)
|
||||||
|
MINIO_PUBLIC_URL=https://cdn.banatie.app
|
||||||
|
API_PUBLIC_URL=https://api.banatie.app
|
||||||
|
|
||||||
|
# CORS
|
||||||
|
CORS_ORIGIN=https://banatie.app,https://api.banatie.app
|
||||||
|
|
||||||
|
# Multi-tenancy defaults
|
||||||
|
DEFAULT_ORG_ID=demo
|
||||||
|
DEFAULT_USER_ID=guest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secrets Environment (`/opt/banatie/secrets.env`)
|
||||||
|
|
||||||
|
**Permissions:** `chmod 600 /opt/banatie/secrets.env`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ===========================================
|
||||||
|
# Banatie Secrets (NEVER COMMIT TO GIT)
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
# Database Password
|
||||||
|
POSTGRES_PASSWORD=<generated_secure_password_32_chars>
|
||||||
|
|
||||||
|
# MinIO Root (Admin Console Access)
|
||||||
|
MINIO_ROOT_USER=banatie_admin
|
||||||
|
MINIO_ROOT_PASSWORD=<generated_secure_password_32_chars>
|
||||||
|
|
||||||
|
# MinIO Service Account (Application Access)
|
||||||
|
MINIO_ACCESS_KEY=banatie_service
|
||||||
|
MINIO_SECRET_KEY=<generated_secure_password_32_chars>
|
||||||
|
|
||||||
|
# AI Services
|
||||||
|
GEMINI_API_KEY=<your_google_gemini_api_key>
|
||||||
|
|
||||||
|
# Security Tokens
|
||||||
|
JWT_SECRET=<generated_64_char_secret>
|
||||||
|
SESSION_SECRET=<generated_32_char_secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL Init Script (`/opt/banatie/scripts/init-db.sql`)
|
||||||
|
|
||||||
|
**Minimal script for PostgreSQL 15 permission fix:**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Banatie PostgreSQL 15 Permission Fix
|
||||||
|
-- This ensures service user can create tables in public schema
|
||||||
|
|
||||||
|
-- PostgreSQL 15 removed default CREATE privileges on public schema
|
||||||
|
-- Grant CREATE permission to database owner
|
||||||
|
GRANT CREATE ON SCHEMA public TO banatie_user;
|
||||||
|
GRANT ALL ON SCHEMA public TO banatie_user;
|
||||||
|
|
||||||
|
-- Note: Actual table creation is handled by Drizzle ORM
|
||||||
|
-- Run 'pnpm --filter @banatie/database db:push' after first deployment
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caddy Integration
|
||||||
|
|
||||||
|
### Caddy Volume Update
|
||||||
|
|
||||||
|
Add to `/opt/services/compose-files/caddy.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
# ... existing volumes ...
|
||||||
|
- /opt/banatie/logs:/opt/banatie/logs # Banatie access logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddyfile Addition
|
||||||
|
|
||||||
|
Add to `/opt/services/configs/caddy/Caddyfile`:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
# ============================================
|
||||||
|
# BANATIE SERVICES (Isolated Startup)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Landing Page (Next.js standalone)
|
||||||
|
banatie.app, www.banatie.app {
|
||||||
|
reverse_proxy banatie-landing:3000
|
||||||
|
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-Frame-Options "DENY"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output file /opt/banatie/logs/landing-access.log {
|
||||||
|
roll_size 10MiB
|
||||||
|
roll_keep 5
|
||||||
|
}
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# API Service (Express.js)
|
||||||
|
api.banatie.app {
|
||||||
|
reverse_proxy banatie-api:3000
|
||||||
|
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-Frame-Options "DENY"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output file /opt/banatie/logs/api-access.log {
|
||||||
|
roll_size 10MiB
|
||||||
|
roll_keep 5
|
||||||
|
}
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# MinIO Console (Admin Interface)
|
||||||
|
storage.banatie.app {
|
||||||
|
reverse_proxy banatie-minio:9001
|
||||||
|
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output file /opt/banatie/logs/storage-access.log {
|
||||||
|
roll_size 10MiB
|
||||||
|
roll_keep 5
|
||||||
|
}
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# CDN - MinIO S3 (Public Images)
|
||||||
|
cdn.banatie.app {
|
||||||
|
reverse_proxy banatie-minio:9000
|
||||||
|
|
||||||
|
# CORS for browser access
|
||||||
|
header {
|
||||||
|
Access-Control-Allow-Origin "*"
|
||||||
|
Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
|
||||||
|
Access-Control-Allow-Headers "Content-Type, Authorization, Range"
|
||||||
|
Access-Control-Expose-Headers "Content-Length, Content-Range"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static images (1 year for immutable content)
|
||||||
|
header /banatie/* {
|
||||||
|
Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output file /opt/banatie/logs/cdn-access.log {
|
||||||
|
roll_size 50MiB
|
||||||
|
roll_keep 10
|
||||||
|
}
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Process
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [ ] VPS access configured (`ssh usul-vps`)
|
||||||
|
- [ ] DNS records configured for banatie.app subdomains
|
||||||
|
- [ ] Gitea access for source code
|
||||||
|
- [ ] GEMINI_API_KEY obtained from Google
|
||||||
|
|
||||||
|
### Initial Deployment
|
||||||
|
|
||||||
|
#### Step 1: Clone Source Code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh usul-vps
|
||||||
|
|
||||||
|
# Create workspace if not exists
|
||||||
|
mkdir -p ~/workspace/projects
|
||||||
|
cd ~/workspace/projects
|
||||||
|
|
||||||
|
# Clone from Gitea
|
||||||
|
git clone ssh://git@git.usul.su:2222/usulpro/banatie-service.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Create Production Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create isolated Banatie directory
|
||||||
|
sudo mkdir -p /opt/banatie/{scripts,logs/api,logs/landing}
|
||||||
|
sudo chown -R usul:usul /opt/banatie
|
||||||
|
|
||||||
|
# Create waitlist-logs directory (Docker will set permissions)
|
||||||
|
mkdir -p /opt/banatie/data/waitlist-logs
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Create Configuration Files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/banatie
|
||||||
|
|
||||||
|
# Create docker-compose.yml (copy from this document)
|
||||||
|
nano docker-compose.yml
|
||||||
|
|
||||||
|
# Create environment files
|
||||||
|
nano .env
|
||||||
|
nano secrets.env
|
||||||
|
chmod 600 secrets.env
|
||||||
|
|
||||||
|
# Create init-db.sql
|
||||||
|
mkdir -p scripts
|
||||||
|
nano scripts/init-db.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 4: Generate Secure Passwords
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate all required secrets
|
||||||
|
echo "POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '\n\r ')" >> secrets.env
|
||||||
|
echo "MINIO_ROOT_USER=banatie_admin" >> secrets.env
|
||||||
|
echo "MINIO_ROOT_PASSWORD=$(openssl rand -base64 32 | tr -d '\n\r ')" >> secrets.env
|
||||||
|
echo "MINIO_ACCESS_KEY=banatie_service" >> secrets.env
|
||||||
|
echo "MINIO_SECRET_KEY=$(openssl rand -base64 32 | tr -d '\n\r ')" >> secrets.env
|
||||||
|
echo "JWT_SECRET=$(openssl rand -base64 64 | tr -d '\n\r ')" >> secrets.env
|
||||||
|
echo "SESSION_SECRET=$(openssl rand -base64 32 | tr -d '\n\r ')" >> secrets.env
|
||||||
|
echo "GEMINI_API_KEY=your_actual_key_here" >> secrets.env
|
||||||
|
|
||||||
|
chmod 600 secrets.env
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 5: Update Caddy Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add Banatie volume to Caddy
|
||||||
|
cd /opt/services
|
||||||
|
nano compose-files/caddy.yml
|
||||||
|
# Add: - /opt/banatie/logs:/opt/banatie/logs
|
||||||
|
|
||||||
|
# Add Banatie routes to Caddyfile
|
||||||
|
nano configs/caddy/Caddyfile
|
||||||
|
# Add Banatie configuration from this document
|
||||||
|
|
||||||
|
# Restart Caddy to apply changes
|
||||||
|
docker compose -f compose-files/caddy.yml --env-file .env up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 6: Remove Old Static Landing (if exists)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove old static deployment
|
||||||
|
sudo rm -rf /var/www/banatie.app
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 7: Build and Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/banatie
|
||||||
|
|
||||||
|
# Build all images
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
docker compose ps
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 8: Initialize Database Schema
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After containers are running, push Drizzle schema
|
||||||
|
cd ~/workspace/projects/banatie-service
|
||||||
|
pnpm install # if not done
|
||||||
|
pnpm --filter @banatie/database db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 9: Verify Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check health endpoints
|
||||||
|
curl -f https://banatie.app
|
||||||
|
curl -f https://api.banatie.app/health
|
||||||
|
curl -f https://storage.banatie.app
|
||||||
|
curl -f https://cdn.banatie.app/minio/health/live
|
||||||
|
|
||||||
|
# Check SSL certificates
|
||||||
|
echo | openssl s_client -servername banatie.app -connect banatie.app:443 2>/dev/null | openssl x509 -noout -dates
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
tail -f /opt/banatie/logs/*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Process
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh usul-vps
|
||||||
|
|
||||||
|
# Pull latest code
|
||||||
|
cd ~/workspace/projects/banatie-service
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Rebuild and redeploy
|
||||||
|
cd /opt/banatie
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d --force-recreate
|
||||||
|
|
||||||
|
# Run migrations if schema changed
|
||||||
|
cd ~/workspace/projects/banatie-service
|
||||||
|
pnpm --filter @banatie/database db:push
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
curl -f https://api.banatie.app/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup Strategy
|
||||||
|
|
||||||
|
### Database Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create backup
|
||||||
|
docker exec banatie-postgres pg_dump -U banatie_user banatie_db > /opt/banatie/backups/banatie_db_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
|
# Restore backup
|
||||||
|
docker exec -i banatie-postgres psql -U banatie_user banatie_db < /opt/banatie/backups/banatie_db_YYYYMMDD.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### MinIO Data Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup MinIO data (requires sudo due to Docker User NS Remapping)
|
||||||
|
sudo tar -czf /opt/banatie/backups/minio_$(date +%Y%m%d).tar.gz -C /opt/banatie/data minio/
|
||||||
|
|
||||||
|
# Restore MinIO data
|
||||||
|
sudo tar -xzf /opt/banatie/backups/minio_YYYYMMDD.tar.gz -C /opt/banatie/data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Complete Banatie backup
|
||||||
|
sudo tar -czf ~/banatie_full_backup_$(date +%Y%m%d).tar.gz \
|
||||||
|
-C /opt banatie/ \
|
||||||
|
--exclude='banatie/data/postgres' \
|
||||||
|
--exclude='banatie/data/minio'
|
||||||
|
|
||||||
|
# Database backup separately
|
||||||
|
docker exec banatie-postgres pg_dump -U banatie_user banatie_db > ~/banatie_db_$(date +%Y%m%d).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Health Check Endpoints
|
||||||
|
|
||||||
|
| Endpoint | Expected | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `https://banatie.app` | 200 | Landing page |
|
||||||
|
| `https://api.banatie.app/health` | 200 JSON | API health |
|
||||||
|
| `https://storage.banatie.app` | 200 | MinIO console |
|
||||||
|
| `https://cdn.banatie.app/minio/health/live` | 200 | MinIO S3 |
|
||||||
|
|
||||||
|
### Log Locations
|
||||||
|
|
||||||
|
| Log | Path |
|
||||||
|
|-----|------|
|
||||||
|
| API Access | `/opt/banatie/logs/api-access.log` |
|
||||||
|
| Landing Access | `/opt/banatie/logs/landing-access.log` |
|
||||||
|
| Storage Console | `/opt/banatie/logs/storage-access.log` |
|
||||||
|
| CDN Access | `/opt/banatie/logs/cdn-access.log` |
|
||||||
|
| API Application | `/opt/banatie/logs/api/` |
|
||||||
|
| Waitlist Emails | `/opt/banatie/data/waitlist-logs/` |
|
||||||
|
|
||||||
|
### Docker Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all Banatie containers
|
||||||
|
docker ps --filter "name=banatie"
|
||||||
|
|
||||||
|
# Check resource usage
|
||||||
|
docker stats --filter "name=banatie"
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker compose -f /opt/banatie/docker-compose.yml logs -f [service]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Container Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker compose -f /opt/banatie/docker-compose.yml logs [service]
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
docker inspect banatie-[service] | jq '.[0].State'
|
||||||
|
|
||||||
|
# Rebuild container
|
||||||
|
docker compose -f /opt/banatie/docker-compose.yml build [service]
|
||||||
|
docker compose -f /opt/banatie/docker-compose.yml up -d [service]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Connection Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test database connectivity
|
||||||
|
docker exec banatie-postgres pg_isready -U banatie_user -d banatie_db
|
||||||
|
|
||||||
|
# Check environment variables
|
||||||
|
docker exec banatie-api env | grep -E 'DATABASE|POSTGRES'
|
||||||
|
|
||||||
|
# Connect to database manually
|
||||||
|
docker exec -it banatie-postgres psql -U banatie_user -d banatie_db
|
||||||
|
```
|
||||||
|
|
||||||
|
### MinIO Access Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check MinIO status
|
||||||
|
docker exec banatie-minio mc admin info local
|
||||||
|
|
||||||
|
# Test S3 API
|
||||||
|
curl -f http://localhost:9000/minio/health/live
|
||||||
|
|
||||||
|
# Check bucket exists
|
||||||
|
docker exec banatie-storage-init mc ls storage/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Issues (Docker User NS Remapping)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check container UID
|
||||||
|
docker exec banatie-postgres id
|
||||||
|
# Expected: uid=70(postgres)
|
||||||
|
|
||||||
|
# Calculate host UID and fix
|
||||||
|
# PostgreSQL: 70 + 165536 = 165606
|
||||||
|
sudo chown -R 165606:165606 /opt/banatie/data/postgres
|
||||||
|
|
||||||
|
# MinIO: 1000 + 165536 = 166536
|
||||||
|
sudo chown -R 166536:166536 /opt/banatie/data/minio
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL Certificate Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Caddy logs
|
||||||
|
docker logs caddy | grep -i "banatie\|certificate\|error"
|
||||||
|
|
||||||
|
# Force certificate renewal
|
||||||
|
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||||
|
|
||||||
|
# Check DNS resolution
|
||||||
|
dig banatie.app +short
|
||||||
|
dig api.banatie.app +short
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Network Security
|
||||||
|
|
||||||
|
- ✅ Internal network (`banatie-internal`) has no external access
|
||||||
|
- ✅ PostgreSQL is not exposed (no `ports:` mapping)
|
||||||
|
- ✅ All external access via Caddy HTTPS only
|
||||||
|
- ✅ MinIO S3 API restricted to GET/HEAD for public access
|
||||||
|
|
||||||
|
### Container Security
|
||||||
|
|
||||||
|
- ✅ Non-root users in application containers
|
||||||
|
- ✅ Docker User Namespace Remapping enabled
|
||||||
|
- ✅ Resource limits can be added as needed
|
||||||
|
- ✅ Health checks for automatic restart
|
||||||
|
|
||||||
|
### Data Security
|
||||||
|
|
||||||
|
- ✅ Secrets stored in separate file with 600 permissions
|
||||||
|
- ✅ Database credentials not exposed
|
||||||
|
- ✅ MinIO service account has limited permissions
|
||||||
|
- ✅ JWT/Session secrets properly generated
|
||||||
|
|
||||||
|
### Access Control
|
||||||
|
|
||||||
|
- ✅ API key authentication required
|
||||||
|
- ✅ Master key for admin operations
|
||||||
|
- ✅ Rate limiting at Caddy level possible
|
||||||
|
- ✅ CORS configured for specific origins
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Document Version**: 1.0
|
||||||
|
**Last Updated**: December 2025
|
||||||
|
**Maintained By**: VPS Project (usul.su)
|
||||||
|
**Related Documentation**: VPS/manuals/banatie-deployment-manual.md
|
||||||
Loading…
Reference in New Issue