diff --git a/docker-compose.localdev.yml b/docker-compose.localdev.yml index 88dab27d..202ca5b8 100644 --- a/docker-compose.localdev.yml +++ b/docker-compose.localdev.yml @@ -1,138 +1,54 @@ -# Self-contained local development stack — Postgres + API + scheduler, all on this machine. +# Self-contained local development stack — Postgres + API + scheduler + worker, all on this machine. +# An OVERRIDE of docker-compose.yml, not a copy of it: only what local development changes lives +# here, so the two cannot drift apart (they had — the worker here lost its wait for the API's +# migrations). # -# 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.yml -f docker-compose.localdev.yml up -d --build # -# 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). - -# Bounded container logs. Docker's json-file driver keeps every line forever by default, and the API -# now writes one structured line per request (E22 S2) — 3×50 MB per service is days of history on a -# busy instance and a hard ceiling on the disk it can take. -x-logging: &logging - driver: json-file - options: - max-size: "50m" - max-file: "3" +# 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. 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 + # Harmless on Docker Desktop; needed on some nested/hardened Docker hosts (every service below). security_opt: - apparmor:unconfined - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siftlode} -d ${POSTGRES_DB:-siftlode}"] - interval: 5s - timeout: 5s - retries: 12 - logging: *logging - 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). + # This instance owns its scheduler (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. + # Downloaded media lives on the host so you can inspect the generated Plex tree. Gitignored. - ./downloads:/downloads - # Plex media, read-only (see the plex_media volume note below). Maps to plex_path_map. - plex_media:/plex-media:ro - logging: *logging - 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"] - # tini as PID 1: reaps orphaned ffmpeg/yt-dlp grandchildren when the watchdog hard-kills a - # download child's process group (the worker reaps its own direct children via subprocess.wait). - init: true - 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 - logging: *logging - 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 - logging: *logging - 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.) + # module is enabled; harmless otherwise. 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. plex_media: driver: local driver_opts: