# 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
