version: "3.9" services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-airpg} POSTGRES_USER: ${POSTGRES_USER:-airpg} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-airpg_secret} volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-airpg}"] interval: 5s timeout: 5s retries: 10 redis: image: redis:7-alpine restart: unless-stopped ports: - "6379:6379" command: ["redis-server", "--appendonly", "yes"] volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 qdrant: image: qdrant/qdrant:v1.11.3 restart: unless-stopped ports: - "6333:6333" - "6334:6334" volumes: - qdrantdata:/qdrant/storage backend: build: context: ./backend dockerfile: Dockerfile restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy qdrant: condition: service_started environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-airpg}:${POSTGRES_PASSWORD:-airpg_secret}@postgres:5432/${POSTGRES_DB:-airpg} REDIS_URL: redis://redis:6379/0 QDRANT_URL: http://qdrant:6333 JWT_SECRET: ${JWT_SECRET:-change_me_in_production_please} ADMIN_SETUP_TOKEN: ${ADMIN_SETUP_TOKEN:-} CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:5173,http://localhost:8080} LOG_LEVEL: ${LOG_LEVEL:-INFO} # Default LLM settings (overridable via admin panel) DEFAULT_LLM_BASE_URL: ${DEFAULT_LLM_BASE_URL:-http://host.docker.internal:1234/v1} DEFAULT_LLM_API_KEY: ${DEFAULT_LLM_API_KEY:-dummy} DEFAULT_LLM_MODEL: ${DEFAULT_LLM_MODEL:-local-model} # Default embeddings settings (overridable via admin panel) DEFAULT_EMBEDDING_PROVIDER: ${DEFAULT_EMBEDDING_PROVIDER:-hash} DEFAULT_EMBEDDING_BASE_URL: ${DEFAULT_EMBEDDING_BASE_URL:-} DEFAULT_EMBEDDING_API_KEY: ${DEFAULT_EMBEDDING_API_KEY:-} DEFAULT_EMBEDDING_MODEL: ${DEFAULT_EMBEDDING_MODEL:-text-embedding-3-small} DEFAULT_EMBEDDING_DIM: ${DEFAULT_EMBEDDING_DIM:-0} DEFAULT_EMBEDDING_REQUEST_TIMEOUT: ${DEFAULT_EMBEDDING_REQUEST_TIMEOUT:-60} # Make `host.docker.internal` resolvable inside the container (Linux). # On Docker Desktop (Mac/Win) this is added automatically; on Linux it's not, # so we add it explicitly. Allows pointing DEFAULT_LLM_BASE_URL at # http://host.docker.internal:1234/v1 to reach an LM Studio / llama.cpp # running on the host. extra_hosts: - "host.docker.internal:host-gateway" ports: - "8000:8000" volumes: - ./backend:/app # Print admin setup token to console on first run stdin_open: true tty: true healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=3)"] interval: 5s timeout: 5s retries: 30 start_period: 30s worker: build: context: ./backend dockerfile: Dockerfile restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy qdrant: condition: service_started backend: condition: service_healthy environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-airpg}:${POSTGRES_PASSWORD:-airpg_secret}@postgres:5432/${POSTGRES_DB:-airpg} REDIS_URL: redis://redis:6379/0 QDRANT_URL: http://qdrant:6333 JWT_SECRET: ${JWT_SECRET:-change_me_in_production_please} LOG_LEVEL: ${LOG_LEVEL:-INFO} DEFAULT_LLM_BASE_URL: ${DEFAULT_LLM_BASE_URL:-http://host.docker.internal:1234/v1} DEFAULT_LLM_API_KEY: ${DEFAULT_LLM_API_KEY:-dummy} DEFAULT_LLM_MODEL: ${DEFAULT_LLM_MODEL:-local-model} DEFAULT_EMBEDDING_PROVIDER: ${DEFAULT_EMBEDDING_PROVIDER:-hash} DEFAULT_EMBEDDING_BASE_URL: ${DEFAULT_EMBEDDING_BASE_URL:-} DEFAULT_EMBEDDING_API_KEY: ${DEFAULT_EMBEDDING_API_KEY:-} DEFAULT_EMBEDDING_MODEL: ${DEFAULT_EMBEDDING_MODEL:-text-embedding-3-small} DEFAULT_EMBEDDING_DIM: ${DEFAULT_EMBEDDING_DIM:-0} DEFAULT_EMBEDDING_REQUEST_TIMEOUT: ${DEFAULT_EMBEDDING_REQUEST_TIMEOUT:-60} WORKER_MODE: "1" command: ["python", "-m", "app.workers.main"] volumes: - ./backend:/app frontend: build: context: ./frontend dockerfile: Dockerfile target: ${FRONTEND_BUILD_TARGET:-dev} restart: unless-stopped depends_on: - backend environment: # Used by Vite's dev-server proxy (vite.config.ts) to forward /api calls. # Inside the frontend container, "localhost" refers to the container itself, # NOT the host — so we point at the docker-compose service name "backend". # The host port (8000) is still published for direct browser/curl access. # Deliberately NOT prefixed with `VITE_` so it cannot leak into the browser # bundle (browser code uses relative "/api" URLs only — see src/api/index.ts). API_PROXY_TARGET: ${API_PROXY_TARGET:-http://backend:8000} ports: - "5173:5173" - "8080:80" volumes: - ./frontend:/app - /app/node_modules volumes: pgdata: redisdata: qdrantdata: