# news-digest — twice-daily LLM-curated briefing. # # Two containers in this stack, both built from the same Dockerfile: # # news-digest-worker — python + cron, runs digest.py at 0800/2000 # local, writes /output/index.html and # /output/edition-*.html. # news-digest-web — FastAPI on uvicorn, serves /output as static # and exposes /api/{hidden,hide,restore} for # the per-item × button (state in # /output/hidden.json, shared across devices). # Homepage card lives on this container. # # Same bind-mounted /output for both: worker writes HTML, web reads # it back. Worker uses atomic writes (.tmp + rename) so partial pages # never get served. services: news-digest-worker: image: local/news-digest:${NEWS_DIGEST_TAG:-v1} build: context: . dockerfile: Dockerfile container_name: news-digest-worker restart: unless-stopped environment: - TZ=${NEWS_DIGEST_TZ:-America/Los_Angeles} - LLAMA_SWAP_URL=${LLAMA_SWAP_URL:-http://10.250.50.54:9292} - LLAMA_SWAP_MODEL=${LLAMA_SWAP_MODEL:-qwen3.5-35-a3b} - LLAMA_SWAP_TIMEOUT=${LLAMA_SWAP_TIMEOUT:-180} - MINIFLUX_URL=${MINIFLUX_URL:-http://miniflux:8080} - MINIFLUX_USER=${MINIFLUX_USER:-lkraven} - MINIFLUX_PASSWORD=${MINIFLUX_PASSWORD} - DIGEST_OUTPUT_DIR=/output - DIGEST_TEMPLATE_DIR=/app/templates - DIGEST_REDDIT_HOURS=${DIGEST_REDDIT_HOURS:-12} - DIGEST_MIN_SCORE=${DIGEST_MIN_SCORE:-50} - DIGEST_MIN_RATIO=${DIGEST_MIN_RATIO:-0.85} - DIGEST_MAX_PER_SUB=${DIGEST_MAX_PER_SUB:-8} - DIGEST_MINIFLUX_HOURS=${DIGEST_MINIFLUX_HOURS:-12} - DIGEST_MINIFLUX_MAX=${DIGEST_MINIFLUX_MAX:-8} - DIGEST_MINIFLUX_TECH_CATEGORY=${DIGEST_MINIFLUX_TECH_CATEGORY:-Tech aggregators} volumes: - ${NEWS_DIGEST_OUTPUT_DIR}:/output networks: - tnet # Cron-driven worker — no healthcheck endpoint. The web container # is what users hit; if the worker dies we'll see stale content. # Restart policy handles transient crashes. news-digest-web: image: local/news-digest:${NEWS_DIGEST_TAG:-v1} build: context: . dockerfile: Dockerfile container_name: news-digest-web restart: unless-stopped depends_on: - news-digest-worker ports: - "${NEWS_DIGEST_BIND:-0.0.0.0}:${NEWS_DIGEST_PORT}:80" volumes: # Read-write here so the API can persist hidden.json. Worker also # writes here (HTML); both processes serialize via filenames they # don't share, plus uvicorn's threading.Lock around hidden.json. - ${NEWS_DIGEST_OUTPUT_DIR}:/output environment: - DIGEST_OUTPUT_DIR=/output # Override the worker's cron entrypoint to launch uvicorn instead. # tini still wraps the process for clean signal forwarding. entrypoint: ["/sbin/tini", "--"] command: ["uvicorn", "web:app", "--host", "0.0.0.0", "--port", "80", "--no-access-log"] healthcheck: test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 30s networks: - tnet labels: - homepage.group=News - homepage.name=Daily Digest - homepage.icon=mdi-newspaper-variant-outline - homepage.description=LLM-curated briefing across feeds, twice daily - homepage.href=http://10.250.50.70:${NEWS_DIGEST_PORT} networks: tnet: name: traefik-net external: true