# Full local observability stack: the API, Prometheus scraping it, and Grafana # pre-provisioned with a dashboard. Brings the whole thing up with one command: # # docker compose up --build # # Then: # API -> http://localhost:8000 (demo at /demo, docs at /docs) # Prom -> http://localhost:9090 # Grafana -> http://localhost:3000 (anonymous viewer; admin/admin to edit) services: api: build: . image: distilbert-emotion-api:local container_name: emotion-api environment: OFFLINE: "1" # flip to 0 (and use requirements-ml) for the real model LOG_LEVEL: "INFO" MAX_BATCH_SIZE: "64" BATCH_MAX_DELAY_MS: "5" ports: - "8000:8000" healthcheck: test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/healthz',timeout=2).status==200 else 1)"] interval: 15s timeout: 3s retries: 5 start_period: 10s restart: unless-stopped prometheus: image: prom/prometheus:v2.53.0 container_name: emotion-prometheus volumes: - ./observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.retention.time=2h" ports: - "9090:9090" depends_on: - api restart: unless-stopped grafana: image: grafana/grafana:11.1.0 container_name: emotion-grafana environment: GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_PASSWORD: admin GF_AUTH_ANONYMOUS_ENABLED: "true" GF_AUTH_ANONYMOUS_ORG_ROLE: Viewer GF_USERS_ALLOW_SIGN_UP: "false" volumes: - ./observability/grafana/provisioning:/etc/grafana/provisioning:ro - ./observability/grafana/dashboards:/var/lib/grafana/dashboards:ro ports: - "3000:3000" depends_on: - prometheus restart: unless-stopped