version: '3.8' services: # PostgreSQL Database db: image: postgres:16-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: build: context: . dockerfile: Dockerfile container_name: schoolcompare_backend ports: - "8000:80" environment: DATABASE_URL: postgresql://schoolcompare:schoolcompare@db:5432/schoolcompare PYTHONUNBUFFERED: 1 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: build: context: ./nextjs-app dockerfile: Dockerfile args: FASTAPI_URL: http://backend:80/api container_name: schoolcompare_nextjs ports: - "3000:3000" environment: NODE_ENV: production # Next.js can access backend via internal network NEXT_PUBLIC_API_URL: http://localhost:8000/api FASTAPI_URL: http://backend:80/api 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 # Kestra — workflow orchestrator (UI at http://localhost:8080) kestra: image: kestra/kestra:latest container_name: schoolcompare_kestra ports: - "8080:8080" volumes: - kestra_storage:/app/storage - ./integrator/flows:/flows environment: KESTRA_CONFIGURATION: | datasources: postgres: url: jdbc:postgresql://db:5432/kestra driverClassName: org.postgresql.Driver username: schoolcompare password: schoolcompare kestra: repository: type: postgres queue: type: postgres storage: type: local local: base-path: /app/storage depends_on: db: condition: service_healthy networks: - schoolcompare-network restart: unless-stopped # Data integrator — Python microservice called by Kestra integrator: build: context: ./integrator dockerfile: Dockerfile container_name: schoolcompare_integrator ports: - "8001:8001" environment: DATABASE_URL: postgresql://schoolcompare:schoolcompare@db:5432/schoolcompare DATA_DIR: /data PYTHONUNBUFFERED: 1 volumes: - ./data:/data depends_on: db: condition: service_healthy networks: - schoolcompare-network restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8001/health"] interval: 30s timeout: 10s retries: 3 start_period: 15s networks: schoolcompare-network: driver: bridge volumes: postgres_data: kestra_storage: