app.bettersight.io/docker-compose.yml

231 lines
9.3 KiB
YAML

networks:
bettersight_default:
# LOCAL DEV — CLAUDE.md §4 uses an external `pangolin_default` network
# (Dokploy/Pangolin-managed, production only). Locally there is no
# Pangolin reverse proxy, so this is a plain compose-managed bridge
# network instead. Do not port this network block to production —
# production still uses the external pangolin_default per CLAUDE.md.
driver: bridge
volumes:
pocketbase_data:
services:
flask-api:
build: ./backend
container_name: flask-api
command: gunicorn app:app -w 4 -b 0.0.0.0:5000
env_file: ./backend/.env
environment:
# Overrides backend/.env's values with container-network-correct
# addresses. host.docker.internal reaches the PocketBase instance
# already running on the host machine (per user instruction, that
# instance is started manually and is NOT the pocketbase service
# below — this compose file never starts its own PocketBase).
- POCKETBASE_URL=http://host.docker.internal:8090
- REDIS_URL=redis://bettersight-redis:6379
- FIRECRAWL_URL=http://firecrawl-api:3002
ports:
- "5050:5000"
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- bettersight_default
restart: unless-stopped
depends_on:
- bettersight-redis
bettersight-worker:
build: ./backend
container_name: bettersight-worker
command: rq worker high normal low -u redis://bettersight-redis:6379
env_file: ./backend/.env
environment:
- POCKETBASE_URL=http://host.docker.internal:8090
- REDIS_URL=redis://bettersight-redis:6379
- FIRECRAWL_URL=http://firecrawl-api:3002
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- bettersight_default
restart: unless-stopped
depends_on:
- bettersight-redis
bettersight-redis:
image: redis:alpine
container_name: bettersight-redis
networks:
- bettersight_default
restart: unless-stopped
# ── Opt-in only — real self-hosted Firecrawl is a 3-service stack
# built from source (no stable published image — see CLAUDE.md §4's
# "Firecrawl — real self-hosted stack" note). Builds from a local
# clone at ./firecrawl (`git clone https://github.com/firecrawl/
# firecrawl.git`) rather than a remote git build context — this
# environment's network couldn't reliably clone the repo itself
# (git's smart-HTTP protocol specifically; plain HTTPS to GitHub's
# API and to PyPI both worked fine, so this was not general
# bandwidth throttling). Re-point back to the GitHub URL below if
# building somewhere with normal git connectivity, and drop the
# local clone from the repo (it's gitignored, not committed).
#
# Build context is the app subdirectory itself (./firecrawl/apps/api),
# not the repo root — apps/api/Dockerfile's COPY instructions (e.g.
# `COPY sharedLibs/go-html-to-md ...`) are written relative to
# apps/api/, matching upstream firecrawl's own docker-compose.yaml
# (`build: apps/api`). Pointing context at the repo root with
# `dockerfile: apps/api/Dockerfile` resolves the Dockerfile from the
# right place but still resolves every COPY inside it against the
# repo root, not apps/api/ — breaks on the first COPY that assumes
# otherwise.
# Firecrawl's NUQ queue backend needs a real Postgres, not just Redis —
# discovered only once firecrawl-api actually started: without
# NUQ_DATABASE_URL set, harness.ts's setupNuqPostgres() tries to
# auto-provision a Postgres container by shelling out to `docker`/
# `podman` from *inside* the firecrawl-api container itself, which
# obviously has neither installed and isn't meant to (no
# Docker-in-Docker here). Firecrawl ships a purpose-built Postgres
# image for this at apps/nuq-postgres/ — pg_cron preloaded plus the
# NUQ schema (nuq.sql) baked in as an initdb script — build that
# rather than pointing at a vanilla postgres image, which would boot
# without the schema NUQ expects.
#
# Setting POSTGRES_HOST to this container's name (anything other
# than "localhost") is what flips setupNuqPostgres() into its
# docker-compose branch, which just builds the connection URL
# directly and skips the Docker/Podman auto-provision path entirely
# — this is the code path Firecrawl's own authors intended for
# exactly this setup, not a workaround.
#
# NUQ_RABBITMQ_URL turned out not to be optional in practice, despite
# most call sites gracefully checking `if (config.NUQ_RABBITMQ_URL)`
# and falling back to polling mode when absent. The harness always
# spawns an extract-worker subprocess (dist/src/services/extract-worker.js)
# regardless of which routes are actually used, and that subprocess's
# consumeExtractJobs() throws immediately without it — no config
# check gates whether it starts. The harness treats any subprocess
# exit as fatal to the whole process, so without RabbitMQ the entire
# firecrawl-api container crash-looped every ~2s even though
# Bettersight never calls /extract. Upstream's own docker-compose.yaml
# confirms this isn't optional either — it lists rabbitmq as a hard
# `depends_on: condition: service_healthy` for the api service. Stock
# image, no build needed.
firecrawl-rabbitmq:
image: rabbitmq:3-management
container_name: firecrawl-rabbitmq
profiles: ["firecrawl"]
networks:
- bettersight_default
restart: unless-stopped
firecrawl-nuq-postgres:
build:
context: ./firecrawl/apps/nuq-postgres
container_name: firecrawl-nuq-postgres
profiles: ["firecrawl"]
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
networks:
- bettersight_default
restart: unless-stopped
# No separate firecrawl-worker service — checked upstream's own
# docker-compose.yaml (firecrawl/docker-compose.yaml) and it defines
# only one `api` container, command `node dist/src/harness.js
# --start-docker`. That harness process spins up the queue workers
# internally (NUQ_WORKER_COUNT etc — see apps/api/src/harness.ts)
# rather than running as a separate process. A prior version of this
# file ran a second container on `pnpm run workers`, which crash
# looped: the runtime image only carries node_modules/dist/native
# (no package.json — see the Dockerfile's runtime stage), so pnpm
# had nothing to run against. This matches the older split-process
# Firecrawl architecture CLAUDE.md's §4 was written against, not the
# current one — do not reintroduce a second container here.
firecrawl-api:
build:
context: ./firecrawl/apps/api
# context: https://github.com/firecrawl/firecrawl.git#main&context=apps/api
container_name: firecrawl-api
profiles: ["firecrawl"]
environment:
- PORT=3002
- HOST=0.0.0.0
- REDIS_URL=redis://bettersight-redis:6379
- REDIS_RATE_LIMIT_URL=redis://bettersight-redis:6379
- PLAYWRIGHT_MICROSERVICE_URL=http://firecrawl-playwright:3000/scrape
- USE_DB_AUTHENTICATION=false
- NUM_WORKERS_PER_QUEUE=8
- POSTGRES_HOST=firecrawl-nuq-postgres
- POSTGRES_PORT=5432
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
- NUQ_RABBITMQ_URL=amqp://firecrawl-rabbitmq:5672
depends_on:
- bettersight-redis
- firecrawl-playwright
- firecrawl-nuq-postgres
- firecrawl-rabbitmq
networks:
- bettersight_default
restart: unless-stopped
firecrawl-playwright:
build:
context: ./firecrawl/apps/playwright-service-ts
# context: https://github.com/firecrawl/firecrawl.git#main&context=apps/playwright-service-ts
container_name: firecrawl-playwright
profiles: ["firecrawl"]
environment:
- PORT=3000
networks:
- bettersight_default
restart: unless-stopped
mem_limit: 4g
# ── Opt-in only — excluded from a plain `docker compose up -d` ────────
# Started with `docker compose --profile with-pocketbase up -d
# pocketbase` if this local host-run instance is ever retired in
# favour of a containerized one. Per explicit instruction: defined
# here for parity with CLAUDE.md §4, but never auto-started while a
# host PocketBase is already running on localhost:8090.
pocketbase:
image: ghcr.io/muchobien/pocketbase:latest
container_name: pocketbase
profiles: ["with-pocketbase"]
volumes:
- pocketbase_data:/pb_data
- ./backend/pb_migrations:/pb_migrations
networks:
- bettersight_default
restart: unless-stopped
# ── Opt-in only — needs MAIL_FROM/SMTP config not present locally ──
freescout:
image: freescout/freescout:latest
container_name: freescout
profiles: ["support"]
environment:
- APP_URL=http://localhost:8080
- MAIL_FROM=support@bettersight.io
networks:
- bettersight_default
restart: unless-stopped
# ── Opt-in only — needs Hetzner S3 credentials not present locally ──
litestream:
image: litestream/litestream:latest
container_name: litestream
profiles: ["backup"]
command: replicate
environment:
- LITESTREAM_ACCESS_KEY_ID=${HETZNER_S3_ACCESS_KEY}
- LITESTREAM_SECRET_ACCESS_KEY=${HETZNER_S3_SECRET_KEY}
networks:
- bettersight_default
restart: unless-stopped