# ============================================================================= # ai-compose.yml -> deployed to ~/ai-compose.yml on yau-sls-poc-lin001 # # House style, inherited from ~/docker-compose.yml (host brief section 7): # - restart: unless-stopped on everything # - log rotation 10 MB x 3 on everything # - NO published host ports: reach services through Caddy on the proxy network # - secrets in 0600 env files under ~/ai/, never here and never in Git # # Orphan-container warnings are expected (shared Compose project name) - ignore. # # docker compose -f ~/ai-compose.yml up -d # ============================================================================= services: # --------------------------------------------------------------------------- # pg-ai - pgvector, reference data, Cube pre-aggregations. # Deliberately NOT on proxy: no UI, nothing outside the AI stack reaches it. # Pinned image - do NOT add to Watchtower's update list. # --------------------------------------------------------------------------- pg-ai: image: pgvector/pgvector:pg16 container_name: pg-ai restart: unless-stopped networks: [ai-internal] env_file: - /home/azureuser/ai/pg-ai.env # 0600, not in Git environment: POSTGRES_DB: plant POSTGRES_USER: postgres PGDATA: /var/lib/postgresql/data/pgdata volumes: - /datadisk/pg-ai:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d plant"] interval: 10s timeout: 5s retries: 5 logging: driver: json-file options: { max-size: "10m", max-file: "3" } # --------------------------------------------------------------------------- # cube - semantic layer. Reads imh over TDS/1433 with the read-only login, or # the fixture tables in pg-ai while USE_FIXTURES=true. Writes pre-aggregations # into pg-ai schema cube_preagg. Pinned - not in Watchtower's list. # --------------------------------------------------------------------------- cube: image: cubejs/cube:v1.1.7 container_name: cube restart: unless-stopped depends_on: pg-ai: condition: service_healthy networks: [ai-internal, proxy] env_file: - /home/azureuser/ai/api.env # 0600, not in Git environment: CUBEJS_DEV_MODE: "false" CUBEJS_LOG_LEVEL: warn # Pre-aggregation store - always pg-ai, whatever the upstream source is. CUBEJS_PRE_AGGREGATIONS_SCHEMA: cube_preagg CUBEJS_EXT_DB_TYPE: postgres CUBEJS_EXT_DB_HOST: pg-ai CUBEJS_EXT_DB_NAME: plant CUBEJS_EXT_DB_USER: cube_rw # CUBEJS_EXT_DB_PASS, CUBEJS_DB_* and CUBEJS_API_SECRET come from api.env. volumes: - /home/azureuser/ai/cube/model:/cube/conf/model:ro healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:4000/readyz || exit 1"] interval: 30s timeout: 5s retries: 3 logging: driver: json-file options: { max-size: "10m", max-file: "3" } # --------------------------------------------------------------------------- # ai-api - FastAPI. Classifier, agent, contracts, guardrails. # --------------------------------------------------------------------------- ai-api: build: context: /home/azureuser/ai/api dockerfile: Dockerfile image: yau/ai-api:local container_name: ai-api restart: unless-stopped depends_on: pg-ai: condition: service_healthy networks: [ai-internal, proxy] env_file: - /home/azureuser/ai/api.env # 0600, not in Git # Phase 9 - the upload inbox, and the ONLY writable path this container # gets. Deliberately not /datadisk/ai-docs: a file that has been uploaded # but not yet approved must not be visible to `ai-ingest --all`. # # COMMENTED OUT UNTIL PHASE 9, on the same principle as caddy/ai-routes.caddy: # add each piece at the phase that needs it. Docker creates a missing bind # source as a ROOT-OWNED directory, and ai-api does not run as root - so # deploying this before the directory exists gives you an inbox the API # cannot write to, on the growing disk of a live shared host. # Create it first, then uncomment: # sudo install -d -o 10002 -g 10002 /datadisk/ai-docs-inbox # volumes: # - /datadisk/ai-docs-inbox:/inbox healthcheck: test: ["CMD", "python", "-m", "app_healthcheck"] interval: 30s timeout: 5s retries: 3 logging: driver: json-file options: { max-size: "10m", max-file: "3" } # --------------------------------------------------------------------------- # ai-web - React/Vite build served by nginx. proxy only; the browser talks to # the API through its public hostname, so it needs nothing on ai-internal. # --------------------------------------------------------------------------- ai-web: build: context: /home/azureuser/ai/web dockerfile: Dockerfile image: yau/ai-web:local container_name: ai-web restart: unless-stopped networks: [proxy] logging: driver: json-file options: { max-size: "10m", max-file: "3" } # --------------------------------------------------------------------------- # ai-ingest - on demand, not a service. Docling -> chunk -> embed -> pg-ai. # docker compose -f ~/ai-compose.yml run --rm ai-ingest --all # The profile keeps it out of `up -d`. # --------------------------------------------------------------------------- ai-ingest: build: context: /home/azureuser/ai/ingest dockerfile: Dockerfile image: yau/ai-ingest:local container_name: ai-ingest profiles: [ingest] restart: "no" networks: [ai-internal] env_file: - /home/azureuser/ai/api.env # 0600, not in Git volumes: - /datadisk/ai-docs:/docs:ro logging: driver: json-file options: { max-size: "10m", max-file: "3" } # --------------------------------------------------------------------------- # ai-docs-worker - Phase 9. The ai-ingest IMAGE with worker.py as entrypoint, # so an uploaded document is parsed and chunked by exactly the same code as a # file ingested from the command line - by construction, not by discipline. # # Two jobs: pre-scan `uploaded` rows for a header proposal, and publish # `approved` ones. It is the only container that can write doc_chunks # (ingest_rw), and it has no HTTP surface and no place on the proxy network. # # /docs is READ-WRITE here, unlike the ai-ingest CLI service above, because # publishing moves the approved file into the folder that determines its # doc_type. That is the one write, and it happens only after a human has # confirmed the header. # # Both mounts must be writable by the image's uid 10002 (ingestuser): # sudo install -d -o 10002 -g 10002 /datadisk/ai-docs-inbox # sudo chown -R 10002:10002 /datadisk/ai-docs # ai-api writes the inbox as its own non-root uid - give the inbox group # write and put both uids in the group rather than making it world-writable. # --------------------------------------------------------------------------- ai-docs-worker: build: context: /home/azureuser/ai/ingest dockerfile: Dockerfile image: yau/ai-ingest:local container_name: ai-docs-worker # Kept out of `up -d` by the profile, exactly as ai-ingest is. worker.py # does not exist yet, so an unguarded service here would crash-loop on a # live shared host. Drop the profile in the same commit that adds the file. # docker compose -f ~/ai-compose.yml --profile worker up -d ai-docs-worker profiles: [worker] restart: unless-stopped depends_on: pg-ai: condition: service_healthy networks: [ai-internal] env_file: - /home/azureuser/ai/api.env # 0600, not in Git entrypoint: ["python", "worker.py"] command: [] volumes: - /datadisk/ai-docs-inbox:/inbox - /datadisk/ai-docs:/docs # rw - see above # Withdrawn documents are MOVED here, not deleted. It is outside the four # doc_type folders on purpose: ingest_file() inserts every chunk with # superseded = FALSE, so a withdrawn file left in /docs comes back LIVE on # the next `ai-ingest --all`. - /datadisk/ai-docs-withdrawn:/withdrawn logging: driver: json-file options: { max-size: "10m", max-file: "3" } networks: ai-internal: driver: bridge proxy: external: true