changemaker.lite/media-manager/docker-compose.yml

574 lines
20 KiB
YAML

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