name: dashboard-cpsp services: database: image: postgres:16-alpine container_name: dashboard-cpsp-database restart: unless-stopped environment: POSTGRES_USER: ${DB_USER:-dashboard} POSTGRES_PASSWORD: ${DB_PASSWORD:-Ket0pr4k} POSTGRES_DB: ${DB_NAME:-dashboard} PGDATA: /var/lib/postgresql/data/pgdata volumes: - db-data:/var/lib/postgresql/data networks: - dashboard-cpsp-network healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${DB_USER:-dashboard} -d ${DB_NAME:-dashboard}", ] interval: 10s timeout: 5s retries: 5 start_period: 10s # Server: SSH tunnel / DBeaver via localhost:15432 (avoids host Postgres on 5432). # Mac dev: add docker-compose.override.yml from docker/compose.override.local.example for :5432. ports: - "127.0.0.1:15432:5432" rag: image: dashboard-cpsp-rag build: context: "./ai-insight" dockerfile: Dockerfile container_name: dashboard-cpsp-rag restart: unless-stopped ports: - "127.0.0.1:5002:5002" environment: RAG_PORT: "5002" HF_HUB_OFFLINE: "1" TRANSFORMERS_OFFLINE: "1" CHROMA_DB_DIR: chroma_db EMBEDDING_MODEL_NAME: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 volumes: # Empty named volume hides image-baked index — run scripts/seed_rag_chroma.* after first up. - rag-chroma:/app/chroma_db networks: - dashboard-cpsp-network llm: image: ollama/ollama:latest container_name: dashboard-cpsp-llm restart: unless-stopped environment: # Plan §6: serialize CPU inference, keep model resident (prefix KV cache reuse). - OLLAMA_NUM_PARALLEL=1 - OLLAMA_MAX_LOADED_MODELS=1 - OLLAMA_KEEP_ALIVE=24h ports: - "127.0.0.1:11434:11434" volumes: - llm-ollama:/root/.ollama networks: - dashboard-cpsp-network api: image: dashboard-cpsp-api build: context: . dockerfile: docker/Dockerfile.api container_name: dashboard-cpsp-api restart: unless-stopped ports: - "127.0.0.1:18000:8000" environment: DEBUG: ${DEBUG:-false} SECRET_KEY: ${SECRET_KEY:-test-secret-key} ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,api,frontend,192.168.192.106,dashboard.local} CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS:-http://localhost,http://127.0.0.1,http://192.168.192.106,http://dashboard.local} CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost,http://127.0.0.1,http://192.168.192.106,http://dashboard.local} CORS_ALLOW_CREDENTIALS: "true" DB_ENGINE: django.db.backends.postgresql DB_NAME: ${DB_NAME:-dashboard} DB_USER: ${DB_USER:-dashboard} DB_PASSWORD: ${DB_PASSWORD:-Ket0pr4k} DB_HOST: database DB_PORT: "5432" KARUNG_WEB_ADMIN_BASE_URL: ${KARUNG_WEB_ADMIN_BASE_URL:-http://host.docker.internal:5000} KARUNG_WEB_ADMIN_TIMEOUT_SECONDS: ${KARUNG_WEB_ADMIN_TIMEOUT_SECONDS:-30} KARUNG_WEB_ADMIN_SYNC_ENABLED: ${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false} DASHBOARD_PUBLISH_HOUR: ${DASHBOARD_PUBLISH_HOUR:-17} DASHBOARD_PUBLISH_MINUTE: ${DASHBOARD_PUBLISH_MINUTE:-0} IOT_SYNC_ENABLED: ${IOT_SYNC_ENABLED:-true} IOT_SYNC_LOOKBACK_MINUTES: ${IOT_SYNC_LOOKBACK_MINUTES:-20} IOT_SYNC_MAX_PAGES: ${IOT_SYNC_MAX_PAGES:-10} CHICKEN_COUNTING_EDGE_BASE_URL: ${CHICKEN_COUNTING_EDGE_BASE_URL:-} CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS: ${CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS:-30} CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED:-true} CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED:-true} SITE_API_KEY: ${SITE_API_KEY:-test-api-key} BOOTSTRAP_ADMIN_USER: ${BOOTSTRAP_ADMIN_USER:-admin} BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD:-Pr04dm1n} BOOTSTRAP_STAFF_USER: ${BOOTSTRAP_STAFF_USER:-skw} BOOTSTRAP_STAFF_PASSWORD: ${BOOTSTRAP_STAFF_PASSWORD:-skw123} PUSAT_BASE_URL: ${PUSAT_BASE_URL:-} PUSAT_API_KEY: ${PUSAT_API_KEY:-} SITE_API_BASE_URL: ${SITE_API_BASE_URL:-} RAG_SERVICE_URL: ${RAG_SERVICE_URL:-http://rag:5002} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://llm:11434} LLM_MODEL_NAME: ${LLM_MODEL_NAME:-qwen2.5:3b} LLM_TIMEOUT_SECONDS: ${LLM_TIMEOUT_SECONDS:-1200} LLM_NUM_CTX: ${LLM_NUM_CTX:-8192} LLM_NUM_PREDICT: ${LLM_NUM_PREDICT:-768} extra_hosts: - "host.docker.internal:host-gateway" volumes: - api-media:/app/media depends_on: database: condition: service_healthy rag: condition: service_started llm: condition: service_started networks: - dashboard-cpsp-network healthcheck: test: [ "CMD", "python", "-c", "import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8000/api/v1/health/'); exit(0 if r.status == 200 else 1)", ] interval: 30s timeout: 5s retries: 3 start_period: 60s cron: image: dashboard-cpsp-cron build: context: . dockerfile: docker/Dockerfile.api container_name: dashboard-cpsp-cron restart: unless-stopped entrypoint: ["/app/entrypoint-cron.sh"] environment: DEBUG: ${DEBUG:-false} SECRET_KEY: ${SECRET_KEY:-test-secret-key} ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,api,frontend,dashboard.local} DB_ENGINE: django.db.backends.postgresql DB_NAME: ${DB_NAME:-dashboard} DB_USER: ${DB_USER:-dashboard} DB_PASSWORD: ${DB_PASSWORD:-Ket0pr4k} DB_HOST: database DB_PORT: "5432" KARUNG_WEB_ADMIN_BASE_URL: ${KARUNG_WEB_ADMIN_BASE_URL:-http://host.docker.internal:5000} KARUNG_WEB_ADMIN_TIMEOUT_SECONDS: ${KARUNG_WEB_ADMIN_TIMEOUT_SECONDS:-30} KARUNG_WEB_ADMIN_SYNC_ENABLED: ${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false} DASHBOARD_PUBLISH_HOUR: ${DASHBOARD_PUBLISH_HOUR:-9} DASHBOARD_PUBLISH_MINUTE: ${DASHBOARD_PUBLISH_MINUTE:-30} IOT_SYNC_ENABLED: ${IOT_SYNC_ENABLED:-true} IOT_SYNC_LOOKBACK_MINUTES: ${IOT_SYNC_LOOKBACK_MINUTES:-20} IOT_SYNC_MAX_PAGES: ${IOT_SYNC_MAX_PAGES:-10} CHICKEN_COUNTING_EDGE_BASE_URL: ${CHICKEN_COUNTING_EDGE_BASE_URL:-} CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS: ${CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS:-30} CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED:-true} CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED:-true} PUSAT_BASE_URL: ${PUSAT_BASE_URL:-} PUSAT_API_KEY: ${PUSAT_API_KEY:-} SITE_API_BASE_URL: ${SITE_API_BASE_URL:-} extra_hosts: - "host.docker.internal:host-gateway" volumes: - api-media:/app/media depends_on: api: condition: service_healthy networks: - dashboard-cpsp-network frontend: image: dashboard-cpsp-frontend build: context: . dockerfile: docker/Dockerfile.web container_name: dashboard-cpsp-frontend restart: unless-stopped ports: - "80:80" depends_on: api: condition: service_healthy networks: - dashboard-cpsp-network healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/health"] interval: 30s timeout: 3s retries: 3 start_period: 10s networks: dashboard-cpsp-network: driver: bridge volumes: db-data: driver: local api-media: driver: local rag-chroma: driver: local llm-ollama: driver: local