services: # PostgreSQL Database postgres: image: postgres:15-alpine restart: unless-stopped environment: POSTGRES_DB: library POSTGRES_USER: mediamanager # REQUIRED: Set POSTGRES_PASSWORD in .env file POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env} volumes: - postgres-data:/var/lib/postgresql/data ports: # Bind to localhost only to prevent external access - "127.0.0.1:5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U mediamanager -d library"] interval: 5s timeout: 5s retries: 5 networks: - media-network # API Server api: build: context: . dockerfile: packages/api/Dockerfile ports: - "3001:3001" volumes: - ./data:/app/data # Docker socket for container management (JoyCaption start/stop) - /var/run/docker.sock:/var/run/docker.sock # Mount local library for organization (read-write for scripts) - /media/bunker-admin/Internal/plex/xxx/media/local:/media/local # Mount studios for scanning (includes OnlyFans as subdirectory) - /media/bunker-admin/Internal/plex/xxx/media/local/studios:/media/studios # Mount public curated content (read-write for copy operations) - /media/bunker-admin/Internal/plex/xxx/media/public/curated:/media/public/curated # Mount playback directory for compilation output (read-write) - /media/bunker-admin/Internal/plex/xxx/media/public/playback:/media/playback # Mount compilations directory for symlinks (read-write) - /media/bunker-admin/Internal/plex/xxx/media/public/compilations:/media/compilations # Mount videos directory for public videos (read-write for copy operations) - /media/bunker-admin/Internal/plex/xxx/media/public/videos:/media/videos # Mount quickies directory for short-form videos (read-write) - /media/bunker-admin/Internal/plex/xxx/media/public/quickies:/media/quickies # Mount gifs directory for compilation scripts - /media/bunker-admin/Internal/plex/xxx/media/local/gifs:/media/gifs # Mount inbox directory for downloaded files - /media/bunker-admin/Internal/plex/xxx/media/local/inbox:/media/inbox # Mount scripts directory (read-only) - ./scripts:/app/scripts:ro # Mount anime models directory for model status checking (read-only) - ./models/anime-pipe:/app/models/anime-pipe:ro # Mount JoyTag models directory for tag seeding (read-only) - ./models/joytag:/app/models/joytag:ro depends_on: postgres: condition: service_healthy extra_hosts: - "host.docker.internal:host-gateway" environment: - DATABASE_URL=postgresql://mediamanager:${POSTGRES_PASSWORD}@postgres:5432/library - DATA_DIR=/app/data - MEDIA_PATH=/media - PUBLIC_DIR=/media/public/curated - SOURCE_LIBRARY=/media/studios - GIFS_PATH=/media/gifs - INBOX_PATH=/media/inbox - PLAYBACK_PATH=/media/playback - COMPILATIONS_PATH=/media/compilations - VIDEOS_PATH=/media/videos - QUICKIES_PATH=/media/quickies - SCRIPTS_PATH=/app/scripts - PORT=3001 # JWT Authentication - REQUIRED: Set JWT_SECRET in .env file - JWT_SECRET=${JWT_SECRET:?JWT_SECRET must be set in .env} # Initial admin user (created on first startup if no admin exists) - INITIAL_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-} - INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD:-} # SMTP Configuration (for email verification and password reset) - SMTP_HOST=${SMTP_HOST:-} - SMTP_PORT=${SMTP_PORT:-587} - SMTP_SECURE=${SMTP_SECURE:-false} - SMTP_USER=${SMTP_USER:-} - SMTP_PASS=${SMTP_PASS:-} - SMTP_FROM=${SMTP_FROM:-noreply@example.com} - SMTP_FROM_NAME=${SMTP_FROM_NAME:-Media Manager} - EMAIL_VERIFICATION_EXPIRY_HOURS=${EMAIL_VERIFICATION_EXPIRY_HOURS:-24} - PASSWORD_RESET_EXPIRY_HOURS=${PASSWORD_RESET_EXPIRY_HOURS:-1} - APP_BASE_URL=${APP_BASE_URL:-http://localhost:3080} # CORS allowed origins (comma-separated list of allowed domains) - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-http://localhost:3080,http://localhost:8080} # Timezone - TZ=${TZ:-UTC} # MaxMind GeoIP Configuration (optional - for session geographic data) # Register free at: https://www.maxmind.com/en/geolite2/signup - MAXMIND_ACCOUNT_ID=${MAXMIND_ACCOUNT_ID:-} - MAXMIND_LICENSE_KEY=${MAXMIND_LICENSE_KEY:-} # Content Safety (Ollama) Configuration - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://ollama:11434} - OLLAMA_MODEL=${OLLAMA_MODEL:-llama-guard3:1b} - SAFETY_CHECK_ENABLED=${SAFETY_CHECK_ENABLED:-true} # Digest Feature Configuration - DIGEST_OLLAMA_URL=${DIGEST_OLLAMA_URL:-http://ollama:11434} - DIGEST_WHISPER_URL=${DIGEST_WHISPER_URL:-http://whisper:5000} # Use abliterated qwen3 models for uncensored scene analysis - DIGEST_VISION_MODEL=${DIGEST_VISION_MODEL:-huihui_ai/qwen3-vl-abliterated:2b} - DIGEST_TEXT_MODEL=${DIGEST_TEXT_MODEL:-huihui_ai/qwen3-abliterated:4b} - DIGEST_FRAME_INTERVAL=${DIGEST_FRAME_INTERVAL:-30} - DIGEST_TEMP_DIR=/app/data/digest_temp # JoyCaption Vision Backend Configuration (alternative to Ollama vision) - DIGEST_VISION_BACKEND=${DIGEST_VISION_BACKEND:-ollama} - JOYCAPTION_URL=${JOYCAPTION_URL:-http://joycaption:8080} - JOYCAPTION_MODEL=${JOYCAPTION_MODEL:-llama-joycaption-beta-one-hf-llava} - JOYCAPTION_FALLBACK_TO_OLLAMA=${JOYCAPTION_FALLBACK_TO_OLLAMA:-true} # Face Recognition Service Configuration - FACE_RECOGNITION_URL=${FACE_RECOGNITION_URL:-http://face-recognition:5001} # YOLO11 Person Detection Service Configuration - YOLO_DETECTION_URL=${YOLO_DETECTION_URL:-http://yolo-detection:5002} # JoyTag Visual Tagging Service Configuration - JOYTAG_URL=${JOYTAG_URL:-http://joytag:5003} - JOYTAG_ENABLED=${JOYTAG_ENABLED:-true} - JOYTAG_THRESHOLD=${JOYTAG_THRESHOLD:-0.35} - JOYTAG_TOP_K=${JOYTAG_TOP_K:-50} # Scene Detection Service Configuration - SCENE_DETECTION_URL=${SCENE_DETECTION_URL:-http://scene-detection:5004} - SCENE_DETECTION_ENABLED=${SCENE_DETECTION_ENABLED:-true} # TransNetV2 Neural Scene Detection Configuration (fast pipeline) - TRANSNET_URL=${TRANSNET_URL:-http://transnetv2:5005} - TRANSNET_ENABLED=${TRANSNET_ENABLED:-true} - TRANSNET_THRESHOLD=${TRANSNET_THRESHOLD:-0.5} # CLIP Embeddings Configuration (fast pipeline diversity scoring) - CLIP_EMBEDDINGS_URL=${CLIP_EMBEDDINGS_URL:-http://clip-embeddings:5006} - CLIP_EMBEDDINGS_ENABLED=${CLIP_EMBEDDINGS_ENABLED:-true} - CLIP_FRAME_BATCH_SIZE=${CLIP_FRAME_BATCH_SIZE:-512} # Container Lifecycle Configuration # Set to 'true' when AI containers run continuously (models fit in VRAM) - DISABLE_CONTAINER_LIFECYCLE=${DISABLE_CONTAINER_LIFECYCLE:-false} # Combined Scene Detection Configuration - ENABLE_COMBINED_SCENE_DETECTION=${ENABLE_COMBINED_SCENE_DETECTION:-true} - SCENE_MERGE_STRATEGY=${SCENE_MERGE_STRATEGY:-weighted} - SCENE_MERGE_TOLERANCE=${SCENE_MERGE_TOLERANCE:-0.75} - SCENE_DETECTION_PARALLEL=${SCENE_DETECTION_PARALLEL:-false} - SCENE_WEIGHT_TRANSNET=${SCENE_WEIGHT_TRANSNET:-1.0} - SCENE_WEIGHT_PYSCENEDETECT=${SCENE_WEIGHT_PYSCENEDETECT:-0.8} - SCENE_WEIGHT_CLIP=${SCENE_WEIGHT_CLIP:-0.9} - CLIP_BOUNDARY_FRAME_INTERVAL=${CLIP_BOUNDARY_FRAME_INTERVAL:-0.5} - CLIP_BOUNDARY_SIMILARITY_THRESHOLD=${CLIP_BOUNDARY_SIMILARITY_THRESHOLD:-0.7} - CLIP_BOUNDARY_MIN_GAP=${CLIP_BOUNDARY_MIN_GAP:-2.0} # Payment System IMAP Configuration - PAYMENT_IMAP_HOST=${PAYMENT_IMAP_HOST:-} - PAYMENT_IMAP_PORT=${PAYMENT_IMAP_PORT:-993} - PAYMENT_IMAP_USER=${PAYMENT_IMAP_USER:-} - PAYMENT_IMAP_PASS=${PAYMENT_IMAP_PASS:-} - PAYMENT_IMAP_TLS=${PAYMENT_IMAP_TLS:-true} - PAYMENT_POLL_INTERVAL_MS=${PAYMENT_POLL_INTERVAL_MS:-60000} - PAYMENT_PROCESSED_FOLDER=${PAYMENT_PROCESSED_FOLDER:-Processed} # PornDB API Configuration (metadata enrichment) - PORNDB_API_KEY=${PORNDB_API_KEY:-} # Inbox File Watcher Configuration (real-time auto-digest) - FILE_WATCHER_ENABLED=${FILE_WATCHER_ENABLED:-false} - FILE_WATCHER_DEBOUNCE_MS=${FILE_WATCHER_DEBOUNCE_MS:-2000} # FFmpeg parallel encoding (default 1 sequential — NVENC is fastest single-stream) - FFMPEG_ENCODE_CONCURRENCY=${FFMPEG_ENCODE_CONCURRENCY:-1} # NVIDIA GPU support for NVENC encoding deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu, video] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s # Admin UI - directly exposed for local dev admin: build: context: . dockerfile: packages/admin/Dockerfile ports: - "8080:3000" environment: - VITE_API_URL=http://api:3001 depends_on: - api networks: - media-network restart: unless-stopped # Public gallery public: build: context: . dockerfile: packages/public/Dockerfile args: - VITE_API_URL=${VITE_API_URL:-http://api:3001} - VITE_VIDEO_URL=${VITE_VIDEO_URL:-http://localhost:8081} ports: - "3080:80" environment: - VITE_API_URL=${VITE_API_URL:-http://api:3001} - VITE_VIDEO_URL=${VITE_VIDEO_URL:-http://localhost:8081} depends_on: - api - nginx networks: - media-network restart: unless-stopped # Nginx for video streaming (public and library content) nginx: image: nginx:alpine ports: - "8081:80" environment: - APP_BASE_URL=${APP_BASE_URL:-http://localhost:3080} volumes: - /media/bunker-admin/Internal/plex/xxx/media/public/curated:/media/public/curated:ro - /media/bunker-admin/Internal/plex/xxx/media/public/playback:/media/playback:ro - /media/bunker-admin/Internal/plex/xxx/media/public/compilations:/media/compilations:ro - /media/bunker-admin/Internal/plex/xxx/media/public/videos:/media/videos:ro - /media/bunker-admin/Internal/plex/xxx/media/public/quickies:/media/quickies:ro - /media/bunker-admin/Internal/plex/xxx/media/local:/media/local:ro - ./data/thumbnails:/media/thumbnails:ro - ./data/ads:/media/ads:ro - ./data/gallery:/media/gallery:ro - ./data/digest_frames:/media/digest_frames:ro - ./nginx/nginx.conf:/etc/nginx/nginx.conf.template:ro command: /bin/sh -c "envsubst '$$APP_BASE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && nginx -g 'daemon off;'" networks: - media-network restart: unless-stopped # Ollama LLM Server # For safety: Uses Llama Guard 3 1B (~1.6GB) for content moderation # For digest: Uses qwen3-vl (vision) and qwen3 (text) for video analysis # Models are automatically pulled on first startup based on OLLAMA_MODELS env var ollama: profiles: ["ai"] build: context: ./ollama dockerfile: Dockerfile ports: - "11435:11434" volumes: - ollama-data:/root/.ollama environment: - OLLAMA_KEEP_ALIVE=24h # Comma-separated list of models to pull on startup # Abliterated qwen3 models for uncensored analysis - OLLAMA_MODELS=${OLLAMA_MODELS:-llama-guard3:1b,huihui_ai/qwen3-vl-abliterated:2b,huihui_ai/qwen3-abliterated:4b} - OLLAMA_CONTEXT_LENGTH=${OLLAMA_CONTEXT_LENGTH:-4096} deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:11434/"] interval: 30s timeout: 10s retries: 5 start_period: 180s # Whisper Transcription Service # Uses faster-whisper for GPU-accelerated speech recognition whisper: profiles: ["ai"] build: context: ./whisper dockerfile: Dockerfile ports: - "5000:5000" volumes: - whisper-models:/root/.cache environment: - WHISPER_MODEL=${WHISPER_MODEL:-base} - WHISPER_DEVICE=${WHISPER_DEVICE:-cuda} - WHISPER_COMPUTE=${WHISPER_COMPUTE:-float16} deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5000/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # JoyCaption Vision Model Server # Uses llama.cpp server with JoyCaption GGUF for image captioning # Requires model files in ./models directory: # - llama-joycaption-beta-one-hf-llava.Q2_K.gguf (~2GB) # - llama-joycaption-beta-one-llava-mmproj-model-f16.gguf (~600MB) # Download from: https://huggingface.co/Mungert/llama-joycaption-beta-one-hf-llava-GGUF joycaption: profiles: ["ai"] image: ghcr.io/ggml-org/llama.cpp:server-cuda ports: - "11436:8080" volumes: - ./models:/models:ro command: - "--model" - "${JOYCAPTION_MODEL_PATH:-/models/llama-joycaption-beta-one-hf-llava.Q2_K.gguf}" - "--mmproj" - "${JOYCAPTION_MMPROJ_PATH:-/models/llama-joycaption-beta-one-llava-mmproj-model-f16.gguf}" - "--host" - "0.0.0.0" - "--port" - "8080" - "--ctx-size" - "${JOYCAPTION_CTX_SIZE:-4096}" - "--n-gpu-layers" - "${JOYCAPTION_GPU_LAYERS:-999}" - "--parallel" - "${JOYCAPTION_PARALLEL:-1}" deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 5 start_period: 120s # Face Recognition Service # Uses InsightFace with ONNX Runtime for face detection and embedding generation # Provides face counting, gender detection, and performer recognition face-recognition: profiles: ["ai"] build: context: ./face-recognition dockerfile: Dockerfile ports: - "5001:5001" volumes: - face-models:/root/.insightface environment: - CUDA_VISIBLE_DEVICES=0 deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5001/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # YOLO11 Person Detection Service (for performer counting) yolo-detection: profiles: ["ai"] build: context: ./yolo-detection dockerfile: Dockerfile ports: - "5002:5002" environment: - MODEL=yolo11s.pt - CONF_THRESHOLD=0.5 - DEVICE=0 deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5002/health')"] interval: 30s timeout: 10s retries: 3 start_period: 120s # JoyTag Visual Tagging Service (for multi-label image tagging) # Uses JoyTag ViT-B/16 for 5000+ Danbooru-style tags with confidence scores # Model auto-downloaded from HuggingFace on first startup (~366MB ONNX) joytag: profiles: ["ai"] build: context: ./joytag dockerfile: Dockerfile ports: - "5003:5003" environment: - CUDA_VISIBLE_DEVICES=0 - CUDA_DEVICE=0 - MODEL_DIR=/models/joytag - PORT=5003 volumes: - ./models:/models deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5003/health"] interval: 30s timeout: 10s retries: 3 start_period: 120s # Scene Detection Service (for hard cut detection in videos) # Uses PySceneDetect to find scene boundaries for cleaner clip transitions # CPU-based - no GPU required scene-detection: profiles: ["ai"] build: context: ./scene-detection dockerfile: Dockerfile ports: - "5004:5004" volumes: # Mount media directories (read-only) for video access - /media/bunker-admin/Internal/plex/xxx/media/local:/media/local:ro - /media/bunker-admin/Internal/plex/xxx/media/public:/media/public:ro # Mount inbox for videos pending processing - /media/bunker-admin/Internal/plex/xxx/media/local/inbox:/media/inbox:ro # Mount digest temp for chunked video processing - ./data/digest_temp:/app/data/digest_temp:ro environment: - SCENE_CONTENT_THRESHOLD=${SCENE_CONTENT_THRESHOLD:-27.0} - SCENE_MIN_LENGTH=${SCENE_MIN_LENGTH:-15} networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5004/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s # TransNetV2 Neural Shot Boundary Detection Service # Uses TransNetV2 for faster and more accurate scene detection # Especially good at detecting soft transitions (dissolves, fades) # Used by fast digest pipeline for scene-based keyframe extraction transnetv2: profiles: ["ai"] build: context: ./transnetv2 dockerfile: Dockerfile ports: - "5005:5005" volumes: # Mount media directories (read-only) for video access - /media/bunker-admin/Internal/plex/xxx/media/local:/media/local:ro - /media/bunker-admin/Internal/plex/xxx/media/public:/media/public:ro - /media/bunker-admin/Internal/plex/xxx/media/local/inbox:/media/inbox:ro # Mount digest temp for chunked video processing - ./data/digest_temp:/app/data/digest_temp:ro environment: - TRANSNET_THRESHOLD=${TRANSNET_THRESHOLD:-0.5} - TRANSNET_MIN_SCENE_LENGTH=${TRANSNET_MIN_SCENE_LENGTH:-1.0} deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5005/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # CLIP Embeddings Service for Visual Diversity Scoring # Uses OpenAI CLIP ViT-B/32 to extract 512-D visual embeddings # Enables diversity-based clip selection without LLM calls # Used by fast digest pipeline for visual similarity measurement clip-embeddings: profiles: ["ai"] build: context: ./clip-embeddings dockerfile: Dockerfile ports: - "5006:5006" environment: - CLIP_MODEL=${CLIP_MODEL:-ViT-B/32} # GPU batch size for embedding extraction (increase if VRAM allows, decrease if OOM) - CLIP_GPU_BATCH_SIZE=${CLIP_GPU_BATCH_SIZE:-512} deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - media-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5006/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s # Newt client for Pangolin tunnel newt: image: fosrl/newt container_name: newt restart: unless-stopped environment: - PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT} - NEWT_ID=${NEWT_ID} - NEWT_SECRET=${NEWT_SECRET} networks: - media-network volumes: postgres-data: ollama-data: whisper-models: face-models: networks: media-network: external: true