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 execfor 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 dataprojects- Projects within organizationsapi_keys- API key authenticationflows- Generation workflow definitionsimages- Image metadata and referencesgenerations- Generation historyprompt_url_cache- URL caching for promptslive_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:
- Create main bucket (
banatie) - Create service account for application access
- Attach readwrite policy to service account
- 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