version: '3.8' services: # PostgreSQL Database with PostGIS db: image: postgis/postgis:16-3.4-alpine container_name: schoolcompare_db environment: POSTGRES_USER: schoolcompare POSTGRES_PASSWORD: schoolcompare POSTGRES_DB: schoolcompare volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" networks: - schoolcompare-network restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U schoolcompare"] interval: 10s timeout: 5s retries: 5 start_period: 10s # FastAPI Backend backend: image: privaterepo.sitaru.org/tudor/school_compare-backend:latest container_name: schoolcompare_backend ports: - "8000:80" environment: DATABASE_URL: postgresql://schoolcompare:schoolcompare@db:5432/schoolcompare PYTHONUNBUFFERED: 1 ADMIN_API_KEY: ${ADMIN_API_KEY:-changeme} TYPESENSE_URL: http://typesense:8108 TYPESENSE_API_KEY: ${TYPESENSE_API_KEY:-changeme} volumes: - ./data:/app/data:ro depends_on: db: condition: service_healthy networks: - schoolcompare-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80/api/data-info"] interval: 30s timeout: 10s retries: 3 start_period: 30s # Next.js Frontend nextjs: image: privaterepo.sitaru.org/tudor/school_compare-frontend:latest container_name: schoolcompare_nextjs ports: - "3000:3000" environment: NODE_ENV: production NEXT_PUBLIC_API_URL: http://localhost:8000/api FASTAPI_URL: http://backend:80/api TYPESENSE_URL: http://typesense:8108 TYPESENSE_API_KEY: ${TYPESENSE_SEARCH_KEY:-changeme} depends_on: backend: condition: service_healthy networks: - schoolcompare-network restart: unless-stopped healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] interval: 30s timeout: 10s retries: 3 start_period: 40s # Typesense — search engine typesense: image: typesense/typesense:30.1 container_name: schoolcompare_typesense ports: - "8108:8108" environment: TYPESENSE_API_KEY: ${TYPESENSE_API_KEY:-changeme} TYPESENSE_DATA_DIR: /data volumes: - typesense_data:/data networks: - schoolcompare-network restart: unless-stopped healthcheck: test: ["CMD-SHELL", "cat < /dev/tcp/localhost/8108"] interval: 15s timeout: 5s retries: 5 start_period: 10s # Apache Airflow — API server + UI (http://localhost:8080) airflow-api-server: image: privaterepo.sitaru.org/tudor/school_compare-pipeline:latest container_name: schoolcompare_airflow_api command: airflow api-server --port 8080 ports: - "8080:8080" environment: &airflow-env AIRFLOW__CORE__EXECUTOR: LocalExecutor AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://schoolcompare:schoolcompare@db:5432/schoolcompare AIRFLOW__CORE__DAGS_FOLDER: /opt/pipeline/dags AIRFLOW__CORE__LOAD_EXAMPLES: "false" AIRFLOW__CORE__SIMPLE_AUTH_MANAGER_USERS: "admin:admin" PG_HOST: db PG_PORT: "5432" PG_USER: schoolcompare PG_PASSWORD: schoolcompare PG_DATABASE: schoolcompare TYPESENSE_URL: http://typesense:8108 TYPESENSE_API_KEY: ${TYPESENSE_API_KEY:-changeme} volumes: - ./pipeline/dags:/opt/pipeline/dags:ro depends_on: db: condition: service_healthy networks: - schoolcompare-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/api/v2/monitor/health"] interval: 30s timeout: 10s retries: 5 start_period: 60s airflow-scheduler: image: privaterepo.sitaru.org/tudor/school_compare-pipeline:latest container_name: schoolcompare_airflow_scheduler command: airflow scheduler environment: *airflow-env volumes: - ./pipeline/dags:/opt/pipeline/dags:ro depends_on: db: condition: service_healthy networks: - schoolcompare-network restart: unless-stopped # One-shot: initialise Airflow metadata DB airflow-init: image: privaterepo.sitaru.org/tudor/school_compare-pipeline:latest container_name: schoolcompare_airflow_init command: airflow db migrate environment: *airflow-env depends_on: db: condition: service_healthy networks: - schoolcompare-network restart: "no" networks: schoolcompare-network: driver: bridge volumes: postgres_data: typesense_data: