services: # ── Database ──────────────────────────────────────────────────────────────── db: image: postgres:16-alpine user: "70:70" # postgres user UID:GID in alpine image (fixed by image) restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} POSTGRES_DB: ${POSTGRES_DB:-destroying_sap} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] interval: 5s timeout: 5s retries: 10 networks: - backend-net # ── Storage service (unified blob storage) ────────────────────────────────── storage-service: build: context: ./features/storage-service dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped environment: STORAGE_BACKEND: local DATA_DIR: /data/storage volumes: - storage_data:/data/storage healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8020/health')\""] interval: 10s timeout: 5s retries: 5 networks: - backend-net # ── Backend (management) ──────────────────────────────────────────────────── backend: build: context: ./backend dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped env_file: ./backend/.env environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-destroying_sap} DOC_SERVICE_URL: http://doc-service:8001 AI_SERVICE_URL: http://ai-service:8010 STORAGE_SERVICE_URL: http://storage-service:8020 depends_on: db: condition: service_healthy storage-service: condition: service_healthy networks: - backend-net # ── AI service (shared AI provider intermediary) ───────────────────────────── ai-service: build: context: ./features/ai-service dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped environment: STORAGE_SERVICE_URL: http://storage-service:8020 depends_on: storage-service: condition: service_healthy networks: - backend-net # ── Doc service (PDF extraction) ──────────────────────────────────────────── doc-service: build: context: ./features/doc-service dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-destroying_sap} AI_SERVICE_URL: http://ai-service:8010 STORAGE_SERVICE_URL: http://storage-service:8020 volumes: - watch_data:/data/watch depends_on: db: condition: service_healthy ai-service: condition: service_started storage-service: condition: service_healthy networks: - backend-net # ── Frontend (UI) ──────────────────────────────────────────────────────────── frontend: build: context: ./frontend dockerfile: Dockerfile network: host user: "1001:1001" restart: unless-stopped ports: - "80:8080" depends_on: - backend networks: - backend-net - frontend-net volumes: postgres_data: storage_data: # All file/blob storage — managed by storage-service (documents + config) watch_data: # Watch directory — bind-mount your NAS/Nextcloud here via docker-compose.override.yml networks: # backend-net: db ↔ backend ↔ doc-service. No host ports bound. # internal:true removed — doc-service needs outbound access for cloud AI providers. backend-net: # External-facing: only the frontend binds a host port through this network. frontend-net: