diff --git a/docs/deployment-plan.md b/docs/deployment-plan.md new file mode 100644 index 0000000..4f7dce9 --- /dev/null +++ b/docs/deployment-plan.md @@ -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 +``` diff --git a/prod-env/.env b/prod-env/.env index 819eeef..03ba978 100644 --- a/prod-env/.env +++ b/prod-env/.env @@ -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 diff --git a/prod-env/docker-compose.yml b/prod-env/docker-compose.yml index e9f2e4f..9856134 100644 --- a/prod-env/docker-compose.yml +++ b/prod-env/docker-compose.yml @@ -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