# Self-contained local development stack — Postgres + API + scheduler, all on this machine. # # Fully decoupled: its own local database (a Docker volume) and its own scheduler, so there # is no shared DB and no migration coupling with any other instance. Edit code, rebuild, and # everything (including the background sync) runs right here. # # docker compose -f docker-compose.localdev.yml up -d --build # # Browse at http://localhost:8080 — Google login works because the OAuth redirect is # http://localhost:8080/auth/callback. The API runs `alembic upgrade head` on startup, so it # applies local migrations to THIS local DB only. Seed the catalog once with a pg_dump from # another instance if you want real data (see RUNBOOK). services: db: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-siftlode} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-siftlode} POSTGRES_DB: ${POSTGRES_DB:-siftlode} volumes: - pgdata:/var/lib/postgresql/data security_opt: - apparmor:unconfined healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siftlode} -d ${POSTGRES_DB:-siftlode}"] interval: 5s timeout: 5s retries: 12 restart: unless-stopped api: build: context: . dockerfile: Dockerfile args: APP_VERSION: ${APP_VERSION:-dev} GIT_SHA: ${GIT_SHA:-unknown} BUILD_DATE: ${BUILD_DATE:-} env_file: .env environment: # Own local database (overrides whatever DATABASE_URL is in .env). DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD:-siftlode}@db:5432/${POSTGRES_DB:-siftlode} # This instance owns its scheduler now (its own DB, so no double-write concern). SCHEDULER_ENABLED: "true" # Download center: the API serves finished files (it does NOT run the worker loop). DOWNLOAD_ROOT: /downloads WORKER_ENABLED: "false" # Plex HLS remux scratch on fast container-local storage (the /downloads bind-mount is a slow # Windows Docker Desktop mount in dev; prod uses fast local disk so it keeps the default). PLEX_HLS_DIR: /var/tmp/plex-hls depends_on: db: condition: service_healthy # Harmless on Docker Desktop; needed on some nested/hardened Docker hosts. security_opt: - apparmor:unconfined ports: - "${APP_PORT:-8080}:8000" volumes: # Downloaded media lives on the host so you can inspect the generated Plex tree. # Gitignored. The worker writes here; the API reads it to serve local downloads. - ./downloads:/downloads # Plex media, read-only (see the plex_media volume note below). Maps to plex_path_map. - plex_media:/plex-media:ro restart: unless-stopped # Dedicated download worker: same image, runs the yt-dlp job loop instead of the API. # It shares the DB (job queue) and the downloads mount with the API. worker: build: context: . dockerfile: Dockerfile args: APP_VERSION: ${APP_VERSION:-dev} GIT_SHA: ${GIT_SHA:-unknown} BUILD_DATE: ${BUILD_DATE:-} command: ["python", "-m", "app.worker"] env_file: .env environment: DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD:-siftlode}@db:5432/${POSTGRES_DB:-siftlode} # The worker must not also run the scheduler; the API owns that. SCHEDULER_ENABLED: "false" DOWNLOAD_ROOT: /downloads WORKER_ENABLED: "true" depends_on: db: condition: service_healthy bgutil-pot: condition: service_started security_opt: - apparmor:unconfined volumes: - ./downloads:/downloads - plex_media:/plex-media:ro restart: unless-stopped # PO-token provider: mints YouTube Proof-of-Origin tokens on demand so the worker bypasses # bot-detection and gets high-quality formats. The worker reaches it at http://bgutil-pot:4416 # (DOWNLOAD_POT_BASE_URL). Pin the tag to match the plugin version in requirements.txt. bgutil-pot: image: brainicism/bgutil-ytdlp-pot-provider:1.3.1 init: true security_opt: - apparmor:unconfined restart: unless-stopped volumes: pgdata: # OPTIONAL (Plex integration dev testing): the Plex media, mounted READ-ONLY so the container can # play the physical file locally (this dev box isn't the Plex host). Only referenced when the Plex # module is enabled; harmless otherwise (Docker mounts it lazily on first container use). On prod # (LXC on the Plex host) this is a plain host bind-mount. # # Local bind to a few test movies/shows kept on this box — the full nas.lan SMB share was too slow. # Point PLEX_MEDIA_HOST_PATH (.env, gitignored, so no site path lives in the tracked compose) at the # Docker-Desktop host path (e.g. /run/desktop/mnt/host/d/plex-test-media for D:\plex-test-media), # mirroring the Plex server's /data//… layout so plex_path_map (/data=/plex-media) resolves. # (The prior CIFS/PLEX_SMB_* approach for the whole nas.lan share lives in git history.) plex_media: driver: local driver_opts: type: none o: bind device: "${PLEX_MEDIA_HOST_PATH:-/run/desktop/mnt/host/d/plex-test-media}"