# ───────────────────────────────────────────────────────────────────────────── # Mulita / PhotoVault — example environment file # # Copy this file to `.env` and adjust the values for your setup. Every key # below has a sensible default in docker-compose.yml, so you only need to # uncomment the ones you actually want to change. # ───────────────────────────────────────────────────────────────────────────── # ── REQUIRED ───────────────────────────────────────────────────────────────── # Host path to your photo library. The compose file mounts this at /photos # inside the backend + worker containers. The backend creates a default # source root pointing at /photos on first boot, so once this is set the # library is scanned with zero further configuration. # # Examples: # macOS / Linux: PHOTO_DIRS=/Users/you/Pictures # Network share: PHOTO_DIRS=/mnt/nas/photos # Windows (WSL): PHOTO_DIRS=/mnt/c/Users/you/Pictures PHOTO_DIRS=./photos # ── PORTS ──────────────────────────────────────────────────────────────────── # Host port the SPA is served on. Browse to http://:/. FRONTEND_PORT=3000 # Host port for the backend API. Almost never needed directly — the frontend # nginx proxies /api/ to the backend over the internal compose network. Kept # exposed for debugging / curl. BACKEND_PORT=8001 # Redis host port. Internal services reach Redis on its container name; this # is just for local debugging. REDIS_PORT=6379 # ── CORS ───────────────────────────────────────────────────────────────────── # Comma-separated list of allowed origins for direct browser access to the # backend. Same-origin requests through the nginx / vite proxy never trip # CORS, so this only matters when something hits the backend port directly # from a different origin (e.g. another machine, dev tools, a reverse proxy # under a different hostname). # # Default "*" is permissive, fine for a single-user homelab. Lock it down in # real deployments: # ALLOWED_ORIGINS=https://photos.example.com # ALLOWED_ORIGINS=https://photos.example.com,http://192.168.1.10:3000 ALLOWED_ORIGINS=* # ── LOGGING / TIMEZONE ─────────────────────────────────────────────────────── # Python log level for the backend and Celery worker. Bump to DEBUG when # chasing scan / thumbnail issues. LOG_LEVEL=INFO # Container timezone. Affects the timestamps in logs and the "added at" # field on newly imported photos. Defaults to UTC. # TZ=Europe/Berlin # TZ=America/New_York TZ=UTC # ── WORKER CONCURRENCY ─────────────────────────────────────────────────────── # # The ingestion pipeline runs on two Celery worker services with separate # concurrency knobs so heavy vision tasks can't starve cheap IO tasks: # # worker-light (default / high / low queues) # Runs: scan, thumbnails, EXIF, pHash, duplicate regrouping. # Mostly IO-bound — 2 prefork children keep a library streaming in. # # worker-vision (vision queue) # Runs: embeddings, object detection, OCR, face extraction, content # classification. Each prefork child loads ~2 GB of ONNX model weights, # so set this to roughly (physical_cores − 1) and watch RAM. # # Defaults target a ~6 core / 16 GB host. Raise these, then # docker compose up -d worker-light worker-vision # to pick them up. Lower for a Pi; go higher on a workstation. # # The old `CELERYD_CONCURRENCY=N` single-worker variable is no longer # read — delete it from your .env if it's set. CELERY_LIGHT_CONCURRENCY=2 CELERY_VISION_CONCURRENCY=5 # ── INTERNAL (rarely overridden) ───────────────────────────────────────────── # These point at the in-compose Redis and the bind-mounted SQLite db. Override # only if you're running Mulita without docker-compose or against an external # Redis. # REDIS_URL=redis://redis:6379 # CELERY_BROKER_URL=redis://redis:6379 # CELERY_RESULT_BACKEND=redis://redis:6379 # DATABASE_URL=sqlite+aiosqlite:////data/db/mulita.db