services: # --- Rust backend -------------------------------------------------------- # The API. Owns db.sqlite3, media/, banners/ and legacy/ via bind mounts. # An in-process scheduler runs the jobs that Celery beat used to (gated by # ENABLE_SCHEDULER — exactly one process must own api_job). SECRET_KEY signs # the SimpleJWT-compatible tokens. backend-rs: build: context: ./backend-rs container_name: holy-rust user: "app" restart: unless-stopped networks: - holy-net ports: - 8001 env_file: - .env environment: - DOMAIN=${DOMAIN:-localhost} - BIND_ADDR=0.0.0.0:8001 - SECRET_KEY=${SECRET_KEY} - DATABASE_URL=sqlite:///srv/backend/db.sqlite3?mode=rw - MEDIA_ROOT=/srv/backend/media - MEDIA_URL=media/ - BANNERS_DIR=/srv/backend/banners - LEGACY_DIR=/srv/legacy - BACKUP_DIR=/srv/backend/backups - FRONTEND_URL=${FRONTEND_URL:-http://${DOMAIN:-localhost}} - JWT_AUTH_COOKIE_SECURE=${JWT_AUTH_COOKIE_SECURE:-true} # Rust owns api_job scheduling (Celery beat retired). - ENABLE_SCHEDULER=${RUST_ENABLE_SCHEDULER:-true} - RUST_LOG=${RUST_LOG:-info,holy_backend_rs=info,sea_orm=warn} volumes: - ./backend:/srv/backend - ./legacy:/srv/legacy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8001/health/"] interval: 10s timeout: 5s retries: 5 start_period: 20s frontend: build: context: ./frontend args: - VITE_API_URL=http://${DOMAIN:-localhost}/api/ - VITE_IMPRESSUM_NAME=${VITE_IMPRESSUM_NAME} - VITE_IMPRESSUM_STREET=${VITE_IMPRESSUM_STREET} - VITE_IMPRESSUM_CITY=${VITE_IMPRESSUM_CITY} - VITE_IMPRESSUM_EMAIL=${VITE_IMPRESSUM_EMAIL} - VITE_IMPRESSUM_PHONE=${VITE_IMPRESSUM_PHONE} container_name: holy-frontend restart: unless-stopped ports: - 80 networks: - holy-net env_file: - .env volumes: # Serve /media/ straight from the shared mount (read-only) so media is # independent of the backend being up. - ./backend/media:/usr/share/nginx/media:ro depends_on: backend-rs: condition: service_healthy networks: holy-net: driver: bridge