# Dev stack: frontend (nginx) + backend (uvicorn --reload, source bind-mounted) # + the hunter (the agent daemon). Postgres is NOT here — every service uses # the existing LAN DB (DATABASE_URL in each .env, kept out of the images by # .dockerignore). The Playwright MCP the agent drives is external too # (PLAYWRIGHT_MCP_URL in agent/.env) and must run with --isolated: the daemon # opens one browser context per job. # # docker compose up --build # frontend http://localhost:8081, API http://localhost:8000 # docker compose --profile vision up --build # + the visual-authenticity sidecar (:8100) and its minio # # (image bytes persist in the minio-data volume, # # DINOv3 weights cache in vision-hf-cache) # # Editing backend/app/** hot-reloads inside the container. Changing # requirements.txt (or frontend/agent code) needs a rebuild: docker compose up --build. services: backend: build: ./backend env_file: backend/.env ports: - "8000:8000" # also lets `npm run dev` (Vite proxy) hit the containerized API # Dev overrides: hot-reload on the mounted source. A production deploy drops # both `command` and `volumes` and runs the image's baked-in CMD. command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload volumes: - ./backend/app:/app/app:ro healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/instance')"] interval: 30s timeout: 5s retries: 3 start_period: 10s agent: build: ./agent # agent/.env is written for host runs (127.0.0.1); the .docker overlay, # loaded second, re-points the DB and Playwright MCP at container names. env_file: - agent/.env - agent/.env.docker restart: unless-stopped # The hunter runs as a daemon: it LISTENs for work and claims it as it # appears, so a UI-triggered hunt or "check prices" starts in seconds # rather than on the next tick. SIGTERM unwinds the pools and hands every # in-flight job back to the queue; the grace period is how long that gets # before the kill. # # The Playwright MCP this talks to (PLAYWRIGHT_MCP_URL in agent/.env.docker) # must run with --isolated: the daemon opens one browser context per job, # and a server on a persistent profile refuses the second one. command: python main.py --serve stop_grace_period: 30s frontend: build: ./frontend ports: - "8081:80" # 8080 is taken by VS Code on this machine depends_on: - backend # --- vision profile (opt-in): docker compose --profile vision up --build ------ # The visual-authenticity sidecar and its object store. Optional by design: # with VISION_SIDECAR_URL unset in backend/agent the feature is off # and plain `docker compose up` is unchanged. LAN-INTERNAL, UNAUTHENTICATED — # publish these ports to trusted interfaces only. minio: image: minio/minio profiles: ["vision"] command: server /data --console-address ":9001" environment: # must match S3_ACCESS_KEY / S3_SECRET_KEY in vision/.env MINIO_ROOT_USER: ${MINIO_ROOT_USER:-snagr-minio} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-snagr-minio-secret} volumes: - minio-data:/data # image bytes persist here # ports: # - "9001:9001" # minio web console — enable for debugging only healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 5s retries: 3 vision: build: ./vision profiles: ["vision"] env_file: vision/.env environment: S3_ENDPOINT: http://minio:9000 # in-network address overrides vision/.env ports: - "8100:8100" # host-run agent / backend dev servers reach it here depends_on: - minio volumes: - vision-hf-cache:/home/vision/.cache/huggingface # weights survive restarts healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8100/health')"] interval: 30s timeout: 5s retries: 3 start_period: 60s # first start may be downloading weights volumes: minio-data: vision-hf-cache: