# Recommended proxy-injection topology. The same immutable image runs as two # isolated non-root roles so API and nginx have independent health/restarts. services: calibre-web: image: crocodilestick/calibre-web-automated:v4.0.6 container_name: calibre-web-automated environment: - PUID=1000 - PGID=1000 - TZ=Etc/UTC volumes: - ./config/cwa:/config - ./books:/calibre-library - ./ingest:/cwa-book-ingest restart: unless-stopped networks: - cwa-net book-translator-api: image: cwa-ebook-translate-plugin:local build: . environment: - BT_ROLE=api # Validate the browser's existing HttpOnly CWA session and derive an # opaque per-session cache tenant. No credential is stored in JS. - BT_AUTH_MODE=cwa_session - BT_CWA_AUTH_URL=http://calibre-web:8083/ajax/emailstat - BT_CWA_AUTH_COOKIE_NAMES=session,remember_token - BT_CWA_AUTH_TIMEOUT_SECONDS=2 - BT_CWA_AUTH_CACHE_TTL_SECONDS=15 - BT_CWA_AUTH_CACHE_MAX_ENTRIES=10000 - BT_CWA_AUTH_MAX_INFLIGHT=8 - BT_CWA_AUTH_MAX_RESPONSE_BYTES=262144 - BT_AUTH_RATE_LIMIT_PER_MINUTE=300 # Credentialed CORS accepts exact BT_ALLOWED_ORIGINS only. This topology # is same-origin through the proxy and therefore needs no CORS grant. - BT_ALLOW_PRIVATE_LAN=false # Only the proxy's fixed address may supply the client X-Forwarded-For. - BT_TRUSTED_PROXIES=172.30.39.3/32 - LLM_PROVIDER=local - LLM_MODEL=gemma4-12b - LLM_API_KEY= - BT_LOCAL_URL=http://host.docker.internal:8000/v1/chat/completions - BT_MAX_CONCURRENT=2 - BT_TIMEOUT=60 - BT_MAX_UPSTREAM_INFLIGHT=2 - BT_UPSTREAM_QUEUE_TIMEOUT=2 - BT_SINGLEFLIGHT_MAX_ENTRIES=1024 - BT_REQUEST_MAX_ATTEMPTS=20 - BT_REQUEST_MAX_INPUT_BYTES=5000000 - BT_REQUEST_MAX_OUTPUT_TOKENS=163840 - BT_REQUEST_DEADLINE_SECONDS=90 - BT_MAX_UPSTREAM_RESPONSE_BYTES=1048576 - BT_CACHE_TTL_DAYS=90 - BT_CACHE_MAX_ENTRIES=100000 extra_hosts: - "host.docker.internal:host-gateway" volumes: - translator-data:/app/data expose: - "8390" read_only: true tmpfs: - /tmp:rw,noexec,nosuid,size=64m,uid=101,gid=102,mode=700 cap_drop: - ALL security_opt: - no-new-privileges:true pids_limit: 256 mem_limit: 1g cpus: 2.0 restart: unless-stopped networks: cwa-net: translator-net: ipv4_address: 172.30.39.2 # Network-scoped alias: the proxy must reach the API through this # fixed-address network, not nondeterministically through cwa-net. aliases: - translator-api book-translator-proxy: image: cwa-ebook-translate-plugin:local environment: - BT_ROLE=proxy - CWA_UPSTREAM=http://calibre-web:8083 - BT_API_UPSTREAM=http://translator-api:8390 # Fixed public authority forwarded to CWA/API. Require an explicit value # so a remote browser is never given localhost as the session authority. - BT_PUBLIC_ORIGIN=${BT_PUBLIC_ORIGIN:?Set BT_PUBLIC_ORIGIN to the exact browser-facing origin} # Finite CWA upload limit; API requests keep their separate 3 MiB cap. - BT_CWA_MAX_BODY_SIZE=${BT_CWA_MAX_BODY_SIZE:-2g} # Must match CWA's "Reverse Proxy Header Name" if that feature is # enabled. The injection proxy strips it because it is not an identity # authority and is directly browser-facing in this topology. - BT_CWA_IDENTITY_HEADER=${BT_CWA_IDENTITY_HEADER:-Remote-User} ports: - "8084:8080" read_only: true tmpfs: - /tmp:rw,noexec,nosuid,size=64m,uid=101,gid=102,mode=700 cap_drop: - ALL security_opt: - no-new-privileges:true pids_limit: 64 mem_limit: 128m cpus: 0.5 depends_on: calibre-web: condition: service_started book-translator-api: condition: service_healthy restart: unless-stopped networks: cwa-net: translator-net: ipv4_address: 172.30.39.3 volumes: translator-data: networks: cwa-net: driver: bridge translator-net: driver: bridge ipam: config: - subnet: 172.30.39.0/24