Storage Media now goes through `storage_service`, which has two backends: the container volume, and S3/MinIO. A volume can only be mounted by one host, has no presigned URLs and no lifecycle rules, none of which suits ~860 MB of media. Reads fall back to the volume when an object is missing, so the existing uploads keep working and files can migrate gradually rather than in one risky pass. A row stores the object key, never a URL: a URL embeds the backend, so a row holding `http://minio:9000/...` breaks the moment the backend changes. MinIO publishes no host ports — the backend reaches it over the compose network, and 9000/9001 are already taken on this host by other stacks. Image libraries (migration d2e3f4a5b6c7) An image belongs to a library, and a person is granted a library the way they are granted a category, so access can be given to some images without giving away all of them. Tags reuse the shared `question_tags` vocabulary rather than inventing a media-only one. Uploads are type- and size-checked, stored through the service, and embedded so an image can be found by what it shows. Classification finished The 316 questions the chooser had declined are now filed with `--force`, which takes the nearest candidate from the same shortlist the chooser saw. 306 were forced, 10 the chooser accepted on this pass. No question sits on a bare system any more: system only 2,730 -> 0 condition/subsystem 214 -> 1,782 full depth 4 -> 1,166 A forced match is a weaker signal than a chosen one, so expect more errors among those 306 — but the original system stays as a cross-link, so nothing is lost and they can be corrected by hand. Tests: 8 new backend covering library scoping, edit confinement, shared-vocabulary tags, storage indirection on upload, and type/size limits. 131 backend green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgRcMaScVEL7TBLpnAoSV9
174 lines
4.7 KiB
YAML
174 lines
4.7 KiB
YAML
services:
|
|
postgres:
|
|
image: pgvector/pgvector:pg16
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: pedquiz
|
|
POSTGRES_USER: pedquiz
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U pedquiz"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
frontend:
|
|
build: ./frontend
|
|
env_file:
|
|
- ./frontend/.env
|
|
ports:
|
|
- "127.0.0.1:8081:80"
|
|
depends_on:
|
|
- backend
|
|
restart: unless-stopped
|
|
|
|
backend:
|
|
build: ./backend
|
|
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
|
|
env_file:
|
|
- ./backend/.env
|
|
environment:
|
|
- ANONYMIZED_TELEMETRY=False
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
volumes:
|
|
- uploads_data:/app/uploads
|
|
- chroma_data:/app/chroma_data
|
|
networks:
|
|
- default
|
|
- danvics_speech
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
restart: unless-stopped
|
|
|
|
celery:
|
|
build: ./backend
|
|
# --beat runs the embedded scheduler (single worker, so no lock needed).
|
|
command: celery -A app.tasks worker --beat --loglevel=${LOG_LEVEL:-info} --concurrency=2
|
|
env_file:
|
|
- ./backend/.env
|
|
environment:
|
|
- ANONYMIZED_TELEMETRY=False
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
volumes:
|
|
- uploads_data:/app/uploads
|
|
- chroma_data:/app/chroma_data
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
restart: unless-stopped
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
volumes:
|
|
- redis_data:/data
|
|
restart: unless-stopped
|
|
|
|
quiz-telegram-bot:
|
|
build: ./telegram-bot
|
|
env_file:
|
|
- ./telegram-bot/.env
|
|
environment:
|
|
- DATABASE_URL=postgresql://pedquiz:${POSTGRES_PASSWORD}@postgres:5432/pedquiz
|
|
- PUBLIC_APP_URL=${APP_URL:-https://pedshub.com}
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
|
|
# ── Logging: Loki + Promtail + Grafana ──────────────────────────────
|
|
loki:
|
|
image: grafana/loki:3.3.2
|
|
restart: unless-stopped
|
|
command: -config.file=/etc/loki/loki-config.yml
|
|
volumes:
|
|
- ./loki/loki-config.yml:/etc/loki/loki-config.yml:ro
|
|
- loki_data:/loki
|
|
ports:
|
|
- "127.0.0.1:3100:3100"
|
|
|
|
promtail:
|
|
image: grafana/promtail:3.3.2
|
|
restart: unless-stopped
|
|
command: -config.file=/etc/promtail/promtail-config.yml
|
|
volumes:
|
|
- ./promtail/promtail-config.yml:/etc/promtail/promtail-config.yml:ro
|
|
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- promtail_positions:/positions
|
|
depends_on:
|
|
- loki
|
|
|
|
grafana:
|
|
image: grafana/grafana:10.3.1
|
|
restart: unless-stopped
|
|
environment:
|
|
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?set GRAFANA_ADMIN_PASSWORD}
|
|
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
|
volumes:
|
|
- grafana_data:/var/lib/grafana
|
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
|
ports:
|
|
- "127.0.0.1:3002:3000"
|
|
depends_on:
|
|
- loki
|
|
|
|
db-backup:
|
|
image: prodrigestivill/postgres-backup-local:16
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_HOST: postgres
|
|
POSTGRES_DB: pedquiz
|
|
POSTGRES_USER: pedquiz
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
|
|
SCHEDULE: "@daily"
|
|
BACKUP_KEEP_DAYS: 14
|
|
BACKUP_KEEP_WEEKS: 4
|
|
BACKUP_KEEP_MONTHS: 6
|
|
HEALTHCHECK_PORT: 8080
|
|
volumes:
|
|
- ./backups:/backups
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
|
|
# Object storage for media. Files this size do not belong in a container
|
|
# volume that only one host can mount, and S3 semantics give presigned URLs
|
|
# and lifecycle rules that a bind mount cannot.
|
|
minio:
|
|
image: minio/minio:RELEASE.2024-10-13T13-34-11Z
|
|
command: server /data --console-address ":9001"
|
|
environment:
|
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-pedshub}
|
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD}
|
|
volumes:
|
|
- minio_data:/data
|
|
# No host ports: the backend reaches MinIO over the compose network, and
|
|
# 9000/9001 are already taken on this host. Publish deliberately if the
|
|
# console is ever needed from outside.
|
|
healthcheck:
|
|
test: ["CMD", "mc", "ready", "local"]
|
|
interval: 20s
|
|
timeout: 5s
|
|
retries: 5
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
uploads_data:
|
|
minio_data:
|
|
chroma_data:
|
|
postgres_data:
|
|
redis_data:
|
|
loki_data:
|
|
grafana_data:
|
|
promtail_positions:
|
|
|
|
networks:
|
|
danvics_speech:
|
|
external: true
|