x-runtime-security: &runtime-security read_only: true cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] init: true x-certificate-a-environment: &certificate-a-environment BOOKAUDIT_PROFILE: production BOOKAUDIT_LIBRARY_PATH: /library BOOKAUDIT_READ_ONLY: "true" BOOKAUDIT_RELEASE_DIGEST: ${BOOKAUDIT_RELEASE_DIGEST:?set the immutable release digest in .env} BOOKAUDIT_QUEUE__BACKEND: valkey BOOKAUDIT_QUEUE__VALKEY_URL: redis://valkey:6379/0 BOOKAUDIT_LOG_JSON: "true" BOOKAUDIT_ALLOW_REMOTE_FILE_UPLOAD: "false" BOOKAUDIT_PRIVACY__ALLOW_REMOTE_TEXT: "false" BOOKAUDIT_PRIVACY__ALLOW_REMOTE_IMAGES: "false" BOOKAUDIT_PROVIDERS__CALIBRE_FETCH: "false" BOOKAUDIT_PROVIDERS__GOOGLE_BOOKS: "true" BOOKAUDIT_PROVIDERS__OPENLIBRARY: "true" BOOKAUDIT_RECOGNITION_V2__OCR__ENABLED: "true" BOOKAUDIT_RECOGNITION_V2__OCR__BACKENDS: '["tesseract"]' BOOKAUDIT_RECOGNITION_V2__VISION__ENABLED: "false" BOOKAUDIT_MANIFESTATION_V2__AUTO_APPLY__ENABLED: "false" BOOKAUDIT_MANIFESTATION_V2__SUPERVISED_PILOT__ENABLED: "false" BOOKAUDIT_EXTRACTORS__TIKA__ENABLED: "false" BOOKAUDIT_VECTORS__ENABLED: "false" BOOKAUDIT_MANGA_MODE__ENABLED: "false" BOOKAUDIT_PAPERLESS__ENABLED: "false" BOOKAUDIT_PREVIEW__GOTENBERG_ENABLED: "false" BOOKAUDIT_OLLAMA_ENABLED: "false" BOOKAUDIT_LMSTUDIO_ENABLED: "false" services: app: <<: *runtime-security build: context: . target: certificate-a image: ${BOOKAUDIT_IMAGE:-calibre-ai-auditor:1.3.2} user: "${UID:-1000}:${GID:-1000}" restart: unless-stopped mem_limit: 512m cpus: 1.0 pids_limit: 128 environment: <<: *certificate-a-environment BOOKAUDIT_DATABASE__BACKEND: postgres BOOKAUDIT_DATABASE__POSTGRES_DSN: ${BOOKAUDIT_APP_POSTGRES_DSN:?set app role DSN in .env} BOOKAUDIT_API_KEY: ${BOOKAUDIT_API_KEY:?set in .env} BOOKAUDIT_TRUSTED_HOSTS: ${BOOKAUDIT_TRUSTED_HOSTS:-localhost,127.0.0.1} BOOKAUDIT_RATE_LIMITS__ENABLED: "true" BOOKAUDIT_RATE_LIMITS__BACKEND: valkey BOOKAUDIT_RATE_LIMITS__VALKEY_URL: redis://valkey:6379/1 volumes: - ./config:/app/config:ro tmpfs: - /tmp:size=128m,mode=1777 ports: - "127.0.0.1:${BOOKAUDIT_PORT:-8080}:8080" networks: ["default", "edge", "monitoring"] depends_on: postgres: condition: service_healthy valkey: condition: service_healthy verifier: condition: service_healthy healthcheck: test: ["CMD-SHELL", "curl -sf -H 'X-API-Key: '$$BOOKAUDIT_API_KEY http://localhost:8080/api/health/ready"] interval: 30s timeout: 5s retries: 3 start_period: 10s verifier: <<: *runtime-security image: ${BOOKAUDIT_IMAGE:-calibre-ai-auditor:1.3.2} user: "${UID:-1000}:${GID:-1000}" restart: unless-stopped mem_limit: 2g cpus: 2.0 pids_limit: 256 command: ["verifier"] environment: <<: *certificate-a-environment BOOKAUDIT_DATABASE__BACKEND: postgres BOOKAUDIT_DATABASE__POSTGRES_DSN: ${BOOKAUDIT_VERIFIER_POSTGRES_DSN:?set verifier role DSN in .env} BOOKAUDIT_VERIFIER__SCRATCH_DIR: /scratch volumes: - ${BOOKAUDIT_LIBRARY_HOST_PATH:?set in .env}:/library:ro - ./config:/app/config:ro tmpfs: - /tmp:size=128m,mode=1777 - /scratch:size=1024m,mode=0700,uid=${UID:-1000},gid=${GID:-1000} depends_on: postgres: condition: service_healthy valkey: condition: service_healthy healthcheck: test: ["CMD", "bookaudit-certificate-a", "verifier-health"] interval: 30s timeout: 5s retries: 3 start_period: 15s postgres: <<: *runtime-security image: postgres:15-alpine@sha256:3d0f7584ed7d04e27fa050d6683a74746608faf21f202be78460d679cc56461f user: "70:70" restart: unless-stopped mem_limit: 1g cpus: 1.0 pids_limit: 128 environment: POSTGRES_USER: bookaudit POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} POSTGRES_DB: bookaudit POSTGRES_APP_PASSWORD: ${POSTGRES_APP_PASSWORD:?set app database password in .env} POSTGRES_VERIFIER_PASSWORD: ${POSTGRES_VERIFIER_PASSWORD:?set verifier database password in .env} POSTGRES_WRITER_PASSWORD: ${POSTGRES_WRITER_PASSWORD:?set writer database password in .env} POSTGRES_MIGRATOR_PASSWORD: ${POSTGRES_MIGRATOR_PASSWORD:?set migrator database password in .env} volumes: - postgres-data:/var/lib/postgresql/data - ./scripts/postgres-init-roles.sh:/docker-entrypoint-initdb.d/10-bookaudit-roles.sh:ro tmpfs: - /tmp:size=64m,mode=1777 - /var/run/postgresql:size=16m,mode=0770,uid=70,gid=70 healthcheck: test: ["CMD-SHELL", "pg_isready -U bookaudit -d bookaudit"] interval: 10s timeout: 5s retries: 5 valkey: <<: *runtime-security image: valkey/valkey:8.1.3-alpine@sha256:d827e7f7552cdee40cc7482dbae9da020f42bc47669af6f71182a4ef76a22773 user: "999:1000" restart: unless-stopped mem_limit: 512m cpus: 0.5 pids_limit: 128 command: ["valkey-server", "--appendonly", "yes", "--save", "60", "1"] volumes: - valkey-data:/data tmpfs: - /tmp:size=32m,mode=1777 healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 3s retries: 5 prometheus: <<: *runtime-security image: prom/prometheus:v3.13.3@sha256:6976aa8a60fec930796ce5772b8d12da7a318a5daa8d40d69c5c7819a05eeed7 profiles: ["monitoring"] user: "${UID:-1000}:${GID:-1000}" restart: unless-stopped mem_limit: 512m cpus: 0.5 pids_limit: 64 command: - --config.file=/etc/prometheus/prometheus.yml - --storage.tsdb.path=/prometheus - --storage.tsdb.retention.time=30d - --storage.tsdb.retention.size=2GB ports: - "127.0.0.1:${BOOKAUDIT_PROMETHEUS_PORT:-19090}:9090" volumes: - ./ops/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./ops/monitoring/alerts.yml:/etc/prometheus/alerts.yml:ro - ./.monitoring/data:/prometheus secrets: - source: bookaudit-api-key target: bookaudit_api_key tmpfs: - /tmp:size=32m,mode=1777 networks: ["monitoring"] depends_on: app: condition: service_healthy healthcheck: test: ["CMD", "promtool", "check", "ready", "--url=http://localhost:9090"] interval: 30s timeout: 5s retries: 3 start_period: 10s caddy: <<: *runtime-security image: ${BOOKAUDIT_EDGE_IMAGE:-calibre-ai-auditor-edge:1.3.2} profiles: ["edge"] user: "${UID:-1000}:${GID:-1000}" restart: unless-stopped ports: - "${BOOKAUDIT_EDGE_BIND_IP:-127.0.0.1}:80:80" - "${BOOKAUDIT_EDGE_BIND_IP:-127.0.0.1}:443:443/tcp" - "${BOOKAUDIT_EDGE_BIND_IP:-127.0.0.1}:443:443/udp" networks: ["edge"] cap_add: ["NET_BIND_SERVICE"] mem_limit: 256m cpus: 0.5 pids_limit: 64 environment: BOOKAUDIT_DOMAIN: ${BOOKAUDIT_DOMAIN:-books.invalid} BOOKAUDIT_BASIC_AUTH_USER: ${BOOKAUDIT_BASIC_AUTH_USER:-auditor} BOOKAUDIT_BASIC_AUTH_HASH: ${BOOKAUDIT_BASIC_AUTH_HASH:-REPLACE_WITH_BCRYPT_HASH} command: ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] volumes: - ./deploy/caddy/Caddyfile.example:/etc/caddy/Caddyfile:ro - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock - ./.edge/data:/data - ./.edge/config:/config tmpfs: - /tmp:size=16m,mode=1777 depends_on: app: condition: service_healthy healthcheck: test: - CMD-SHELL - >- wget -S --spider "https://$${BOOKAUDIT_DOMAIN}/" 2>&1 | grep -Eq 'HTTP/[0-9.]+ 401' interval: 30s timeout: 10s retries: 3 start_period: 60s provision-roles: <<: *runtime-security image: postgres:15-alpine@sha256:3d0f7584ed7d04e27fa050d6683a74746608faf21f202be78460d679cc56461f profiles: ["maintenance"] user: "70:70" restart: "no" entrypoint: ["/opt/bookaudit/postgres-provision-roles.sh"] environment: HOME: /tmp PGHOST: postgres PGPASSWORD: ${POSTGRES_PASSWORD:?set in .env} POSTGRES_USER: bookaudit POSTGRES_DB: bookaudit POSTGRES_APP_PASSWORD: ${POSTGRES_APP_PASSWORD:?set app database password in .env} POSTGRES_VERIFIER_PASSWORD: ${POSTGRES_VERIFIER_PASSWORD:?set verifier database password in .env} POSTGRES_WRITER_PASSWORD: ${POSTGRES_WRITER_PASSWORD:?set writer database password in .env} POSTGRES_MIGRATOR_PASSWORD: ${POSTGRES_MIGRATOR_PASSWORD:?set migrator database password in .env} volumes: - ./scripts/postgres-init-roles.sh:/opt/bookaudit/postgres-provision-roles.sh:ro tmpfs: - /tmp:size=16m,mode=0700,uid=70,gid=70 depends_on: postgres: condition: service_healthy migrate: <<: *runtime-security image: ${BOOKAUDIT_IMAGE:-calibre-ai-auditor:1.3.2} profiles: ["maintenance"] restart: "no" command: ["migrate"] environment: BOOKAUDIT_PROFILE: production BOOKAUDIT_DATABASE__BACKEND: postgres BOOKAUDIT_DATABASE__POSTGRES_DSN: ${BOOKAUDIT_MIGRATOR_POSTGRES_DSN:?set migrator role DSN in .env} volumes: - ./config:/app/config:ro tmpfs: - /tmp:size=64m,mode=1777 depends_on: postgres: condition: service_healthy writer: <<: *runtime-security build: context: . target: writer image: ${BOOKAUDIT_WRITER_IMAGE:-calibre-ai-auditor-writer:1.3.2} profiles: ["writer"] user: "${UID:-1000}:${GID:-1000}" restart: unless-stopped command: ["writer"] environment: BOOKAUDIT_PROFILE: production BOOKAUDIT_LIBRARY_PATH: /library BOOKAUDIT_ARTIFACTS_DIR: /writer-artifacts BOOKAUDIT_DATABASE__BACKEND: postgres BOOKAUDIT_DATABASE__POSTGRES_DSN: ${BOOKAUDIT_WRITER_POSTGRES_DSN:?set writer role DSN in .env} BOOKAUDIT_QUEUE__BACKEND: valkey BOOKAUDIT_QUEUE__VALKEY_URL: redis://valkey:6379/0 BOOKAUDIT_READ_ONLY: "false" BOOKAUDIT_LOG_JSON: "true" volumes: - ${BOOKAUDIT_LIBRARY_HOST_PATH:?set in .env}:/library:rw - ./.writer-artifacts:/writer-artifacts - ./config:/app/config:ro tmpfs: - /tmp:size=256m,mode=1777 depends_on: postgres: condition: service_healthy valkey: condition: service_healthy healthcheck: test: ["CMD", "bookaudit", "writer-health"] interval: 30s timeout: 5s retries: 3 start_period: 10s retention: <<: *runtime-security image: ${BOOKAUDIT_WRITER_IMAGE:-calibre-ai-auditor-writer:1.3.2} profiles: ["writer-maintenance"] user: "${UID:-1000}:${GID:-1000}" restart: "no" command: ["retention"] environment: BOOKAUDIT_PROFILE: production BOOKAUDIT_ARTIFACTS_DIR: /writer-artifacts BOOKAUDIT_DATABASE__BACKEND: postgres BOOKAUDIT_DATABASE__POSTGRES_DSN: ${BOOKAUDIT_WRITER_POSTGRES_DSN:?set writer role DSN in .env} BOOKAUDIT_QUEUE__BACKEND: valkey BOOKAUDIT_QUEUE__VALKEY_URL: redis://valkey:6379/0 volumes: - ./.writer-artifacts:/writer-artifacts - ${BOOKAUDIT_BACKUP_HOST_PATH:-./backups}:/backups:ro - ./config:/app/config:ro tmpfs: - /tmp:size=64m,mode=1777 depends_on: postgres: condition: service_healthy valkey: condition: service_healthy volumes: postgres-data: valkey-data: networks: edge: driver_opts: com.docker.network.bridge.enable_ip_masquerade: "false" monitoring: driver_opts: com.docker.network.bridge.enable_ip_masquerade: "false" secrets: bookaudit-api-key: file: ./.monitoring/bookaudit_api_key