services: kafka: image: apache/kafka:3.9.0 ports: - "9092:9092" environment: KAFKA_NODE_ID: "1" KAFKA_PROCESS_ROLES: "broker,controller" KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093" KAFKA_LISTENERS: "PLAINTEXT://:9092,CONTROLLER://:9093" KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka:9092" KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER" KAFKA_INTER_BROKER_LISTENER_NAME: "PLAINTEXT" CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk" KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" KAFKA_NUM_PARTITIONS: "3" healthcheck: test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list"] interval: 10s timeout: 5s retries: 15 deploy: resources: limits: memory: 1G mlflow: image: python:3.12-slim command: > bash -c "pip install mlflow>=2.13 --quiet --no-cache-dir && mlflow server --backend-store-uri file:///mlruns --default-artifact-root file:///mlruns --host 0.0.0.0 --port 5000" ports: - "5000:5000" volumes: - ./data/mlruns:/mlruns spark: build: ./services/spark-jobs command: > bash -c "until [ -f /data/ml-1m/ratings.dat ]; do echo 'Waiting for bootstrap data...'; sleep 5; done && python streaming.py" environment: SPARK_MASTER: "local[*]" REDIS_URL: "redis://redis:6379" POSTGRES_URL: "postgresql://recsys:recsys@postgres:5432/recsys" KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" DATA_DIR: "/data" CHECKPOINT_DIR: "/tmp/spark-checkpoint" MLFLOW_TRACKING_URI: "http://mlflow:5000" PYTHONUNBUFFERED: "1" volumes: - ./data:/data - spark-checkpoint:/tmp/spark-checkpoint depends_on: kafka: { condition: service_healthy } redis: { condition: service_healthy } postgres: { condition: service_healthy } deploy: resources: limits: memory: 2G restart: on-failure event-simulator: build: ./services/event-simulator environment: KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" DATA_DIR: "/data" EVENT_RATE: "5" volumes: - ./data:/data depends_on: kafka: { condition: service_healthy } restart: on-failure api: build: ./services/api ports: - "8000:8000" environment: REDIS_URL: "redis://redis:6379" POSTGRES_URL: "postgresql://recsys:recsys@postgres:5432/recsys" KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" depends_on: redis: { condition: service_healthy } postgres: { condition: service_healthy } kafka: { condition: service_healthy } healthcheck: test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/health')\""] interval: 10s timeout: 5s retries: 5 restart: on-failure redis: image: redis:7-alpine ports: - "6379:6379" command: redis-server --save 60 1 --appendonly yes volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 postgres: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-recsys} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-recsys} POSTGRES_DB: ${POSTGRES_DB:-recsys} ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data - ./services/api/schema.sql:/docker-entrypoint-initdb.d/schema.sql healthcheck: test: ["CMD-SHELL", "pg_isready -U recsys"] interval: 5s timeout: 3s retries: 10 frontend: build: ./services/frontend ports: - "3000:80" depends_on: api: { condition: service_healthy } prometheus: image: prom/prometheus:v2.51.0 ports: - "9090:9090" volumes: - ./infra/prometheus.yml:/etc/prometheus/prometheus.yml:ro grafana: image: grafana/grafana:10.4.3 ports: - "3001:3000" environment: GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin} GF_AUTH_ANONYMOUS_ENABLED: "true" GF_AUTH_ANONYMOUS_ORG_ROLE: Viewer volumes: - grafana-data:/var/lib/grafana - ./infra/grafana/datasources:/etc/grafana/provisioning/datasources:ro - ./infra/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro volumes: postgres-data: grafana-data: spark-checkpoint: redis-data: