pg_orrery/search/docker-compose.yml
Ryan Malloy 317f74b33b Add search backend: FastAPI + FastMCP + pgvector for docs Q&A and live SQL
Search stack replicates the Hamilton site pattern with pg_orrery-specific
additions:

- FastAPI REST API (chat SSE streaming, semantic search, health check)
- FastMCP server at /mcp with doc search and live SQL query tools
- pgvector + pgai vectorizer for 1024-dim document embeddings
- Hybrid search (semantic cosine + text ILIKE with pg_trgm GIN)
- Dual LLM backend: self-hosted qwen3 via GPU gateway or Anthropic Claude
- Live read-only pg_orrery SQL execution with safety guardrails
  (SELECT-only validation, read-only transaction, 5s timeout, 100-row cap)
- Convenience MCP tools: planet_position, sky_survey, satellite_pass
- MDX content ingestion from docs/src/content/docs/ (50 pages)
- Docker Compose: pg_orrery+pgvector DB, pgai, vectorizer-worker, API
- Alembic async migrations, Makefile, .env.example
2026-03-01 15:42:14 -07:00

135 lines
3.9 KiB
YAML

services:
db:
build:
context: .
dockerfile: Dockerfile.db
environment:
POSTGRES_DB: orrery_search
POSTGRES_USER: ${POSTGRES_USER:-orrery}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pg-data:/var/lib/postgresql/data
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U orrery -d orrery_search"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
pgai-install:
image: timescale/pgai-vectorizer-worker:latest
entrypoint: ["python", "-m", "pgai", "install", "-d", "postgresql://${POSTGRES_USER:-orrery}:${POSTGRES_PASSWORD}@db:5432/orrery_search"]
networks:
- internal
depends_on:
db:
condition: service_healthy
restart: "no"
vectorizer-worker:
image: timescale/pgai-vectorizer-worker:latest
environment:
PGAI_VECTORIZER_WORKER_DB_URL: postgresql://${POSTGRES_USER:-orrery}:${POSTGRES_PASSWORD}@db:5432/orrery_search
OPENAI_BASE_URL: ${GPU_BASE_URL:-https://orrery-search.gpu.supported.systems/v1}
OPENAI_API_KEY: ${GPU_API_KEY}
command: ["--poll-interval", "5s"]
networks:
- internal
depends_on:
db:
condition: service_healthy
pgai-install:
condition: service_completed_successfully
restart: unless-stopped
api-dev:
build:
context: .
dockerfile: Dockerfile
target: dev
profiles: ["dev"]
env_file: .env
volumes:
- ./src:/app/src
- ./alembic:/app/alembic
- ../docs/src/content/docs:/data/content:ro
networks:
- internal
- caddy
depends_on:
db:
condition: service_healthy
pgai-install:
condition: service_completed_successfully
labels:
caddy: ${DOMAIN:-pg-orrery.warehack.ing}
caddy.handle: /api/search*
caddy.handle.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_1: /health
caddy.handle_1.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_2: /mcp*
caddy.handle_2.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_3: /api/chat*
caddy.handle_3.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_3.0_reverse_proxy.flush_interval: "-1"
caddy.handle_3.0_reverse_proxy.transport: "http"
caddy.handle_3.0_reverse_proxy.transport.read_timeout: "0"
caddy.handle_3.0_reverse_proxy.transport.write_timeout: "0"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
restart: unless-stopped
api-prod:
build:
context: .
dockerfile: Dockerfile
target: prod
profiles: ["prod"]
env_file: .env
volumes:
- ../docs/src/content/docs:/data/content:ro
networks:
- internal
- caddy
depends_on:
db:
condition: service_healthy
pgai-install:
condition: service_completed_successfully
labels:
caddy: ${DOMAIN:-pg-orrery.warehack.ing}
caddy.handle: /api/search*
caddy.handle.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_1: /health
caddy.handle_1.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_2: /mcp*
caddy.handle_2.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_3: /api/chat*
caddy.handle_3.0_reverse_proxy: "{{upstreams 8000}}"
caddy.handle_3.0_reverse_proxy.flush_interval: "-1"
caddy.handle_3.0_reverse_proxy.transport: "http"
caddy.handle_3.0_reverse_proxy.transport.read_timeout: "0"
caddy.handle_3.0_reverse_proxy.transport.write_timeout: "0"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
restart: unless-stopped
volumes:
pg-data:
networks:
internal:
caddy:
external: true