# Server (Proxmox LXC) deployment of the full Siftlode stack. # # This is the "central" instance: it owns the single shared Postgres database and # runs the background scheduler (RSS poll, enrichment, recent/deep backfill) 24/7. # Local dev machines and any future VPS point their DATABASE_URL at this Postgres # and run with SCHEDULER_ENABLED=false (see docker-compose.localdev.yml) so exactly # one scheduler ever writes to the DB. # # Usage (inside the LXC, from the project root which is the bind-mounted /docker): # export COMPOSE_FILE=docker-compose.server.yml # or put it in .env # docker compose up -d --build # # Postgres data lives in ./pgdata (a host-visible bind mount under /docker/subfeed) # so it is covered by the host's /docker backups; logical pg_dump backups via # scripts/backup.sh remain the portable mechanism for moving between hosts. services: db: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-subfeed} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-subfeed} POSTGRES_DB: ${POSTGRES_DB:-subfeed} volumes: - ./pgdata:/var/lib/postgresql/data ports: # Published on the LAN so the local dev webapp uses this central DB. - "5432:5432" # Required for Docker-in-LXC: the container can't load the docker-default # AppArmor profile inside an unprivileged-from-AppArmor's-view namespace. security_opt: - apparmor:unconfined healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-subfeed} -d ${POSTGRES_DB:-subfeed}"] interval: 5s timeout: 5s retries: 12 restart: unless-stopped api: build: context: . dockerfile: Dockerfile env_file: .env environment: DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD:-subfeed}@db:5432/${POSTGRES_DB:-subfeed} # This instance owns the background sync. Keep it true here and false everywhere else. SCHEDULER_ENABLED: "true" depends_on: db: condition: service_healthy security_opt: - apparmor:unconfined ports: # Exposed on the LAN for status/health checks only. Interactive Google login # needs a public HTTPS redirect URL (set up later); backfill runs regardless. - "${APP_PORT:-8080}:8000" healthcheck: test: ["CMD", "python", "-c", "import urllib.request; exit(0 if urllib.request.urlopen('http://localhost:8000/healthz').status == 200 else 1)"] interval: 15s timeout: 5s retries: 5 start_period: 25s restart: unless-stopped