diff --git a/infrastructure-v1.md b/infrastructure-v1.md new file mode 100644 index 0000000..4677d08 --- /dev/null +++ b/infrastructure-v1.md @@ -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= + +# MinIO Root (Admin Console Access) +MINIO_ROOT_USER=banatie_admin +MINIO_ROOT_PASSWORD= + +# MinIO Service Account (Application Access) +MINIO_ACCESS_KEY=banatie_service +MINIO_SECRET_KEY= + +# AI Services +GEMINI_API_KEY= + +# Security Tokens +JWT_SECRET= +SESSION_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