docs: deploying instruction

This commit is contained in:
Oleg Proskurin 2025-12-14 15:03:02 +07:00
parent 77006e8f47
commit 6defdbafc4
1 changed files with 947 additions and 0 deletions

947
infrastructure-v1.md Normal file
View File

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