chore: rebrand Subfeed -> Siftlode
Rename all user-facing references (UI wordmark Sift+lode, titles, app name,
legal pages, onboarding wizard, emails, README/docs) and infra paths
(/srv/subfeed -> /srv/siftlode, image tag, deploy script, backup filenames).
Internal identifiers kept on purpose: Postgres user/db "subfeed", logger
namespace, localStorage keys, and the subfeed_pgdata volume (renaming would
orphan the migrated production data).
2026-06-14 04:40:22 +02:00
|
|
|
# Server (Proxmox LXC) deployment of the full Siftlode stack.
|
2026-06-11 16:35:00 +02:00
|
|
|
#
|
|
|
|
|
# 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"
|
2026-06-11 16:42:44 +02:00
|
|
|
# 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
|
2026-06-11 16:35:00 +02:00
|
|
|
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
|
2026-06-15 00:06:57 +02:00
|
|
|
args:
|
|
|
|
|
APP_VERSION: ${APP_VERSION:-dev}
|
|
|
|
|
GIT_SHA: ${GIT_SHA:-unknown}
|
|
|
|
|
BUILD_DATE: ${BUILD_DATE:-}
|
2026-06-11 16:35:00 +02:00
|
|
|
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
|
2026-06-11 16:42:44 +02:00
|
|
|
security_opt:
|
|
|
|
|
- apparmor:unconfined
|
2026-06-11 16:35:00 +02:00
|
|
|
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
|