From f848b9f15a3e201fc83cbb5571dc6fa971359e90 Mon Sep 17 00:00:00 2001 From: JasonFraser Date: Thu, 2 Jul 2026 11:22:47 -0400 Subject: [PATCH] Add local dev Docker stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backend/Dockerfile and a root docker-compose.yml for running flask-api, bettersight-worker, bettersight-redis, and firecrawl locally — needed to exercise the dashboard against a real backend instead of mocks. Firecrawl and PocketBase are gated behind opt-in compose profiles since self-hosted Firecrawl builds from source (slow) and PocketBase is typically run separately in dev. Co-Authored-By: Claude Sonnet 5 --- backend/Dockerfile | 23 +++++++ docker-compose.yml | 163 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..42af089 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12-slim + +WORKDIR /app + +# System deps for Playwright's Chromium (playwright install-deps covers the +# actual browser dependencies below; git/curl are just useful in-container +# for debugging import/dependency issues without rebuilding). +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Installs the Chromium binary + the OS-level libraries it needs +# (fonts, codecs, etc.) — must run after playwright is pip-installed. +RUN playwright install --with-deps chromium + +COPY . . + +EXPOSE 5000 + +CMD ["gunicorn", "app:app", "-w", "4", "-b", "0.0.0.0:5000"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..32d4feb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,163 @@ +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). The build is slow + # (playwright-service alone pulls in Chromium + fonts), and Firecrawl + # is only used for on-demand /v1/map trip-intent matching — not + # required for viewing dashboard pages. Start explicitly with + # `docker compose --profile firecrawl up -d` when trip-intent + # matching needs to be exercised locally. + firecrawl-api: + build: + context: https://github.com/firecrawl/firecrawl.git#main + dockerfile: apps/api/Dockerfile + 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 + depends_on: + - bettersight-redis + - firecrawl-playwright + networks: + - bettersight_default + restart: unless-stopped + + firecrawl-worker: + build: + context: https://github.com/firecrawl/firecrawl.git#main + dockerfile: apps/api/Dockerfile + container_name: firecrawl-worker + profiles: ["firecrawl"] + command: pnpm run workers + environment: + - 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 + depends_on: + - bettersight-redis + - firecrawl-playwright + networks: + - bettersight_default + restart: unless-stopped + + firecrawl-playwright: + build: + context: https://github.com/firecrawl/firecrawl.git#main + dockerfile: apps/playwright-service-ts/Dockerfile + 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