banatie-service/infrastructure-v1.md

26 KiB

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

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:

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

{
  "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:

# 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:

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

# 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)

# ===========================================
# 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

# ===========================================
# 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:

-- 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:

volumes:
  # ... existing volumes ...
  - /opt/banatie/logs:/opt/banatie/logs  # Banatie access logs

Caddyfile Addition

Add to /opt/services/configs/caddy/Caddyfile:

# ============================================
# 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

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

# 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

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

# 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

# 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)

# Remove old static deployment
sudo rm -rf /var/www/banatie.app

Step 7: Build and Deploy

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

# 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

# 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

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

# 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

# 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

# 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

# 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

# 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

# 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

# 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)

# 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

# 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