services: db: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-langlearn} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-langlearn} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-langlearn}"] interval: 10s timeout: 5s retries: 10 restart: unless-stopped deploy: resources: limits: cpus: "1" memory: 1G api: build: ./api ports: - "${API_PORT:-8000}:8000" command: sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 2" environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-langlearn}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-langlearn} ADMIN_USER_EMAILS: ${ADMIN_USER_EMAILS} API_BASE_URL: ${API_BASE_URL} JWT_SECRET: ${JWT_SECRET} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} DEEPL_API_KEY: ${DEEPL_API_KEY} DEEPGRAM_API_KEY: ${DEEPGRAM_API_KEY} GEMINI_API_KEY: ${GEMINI_API_KEY} PYTHONPATH: /app STORAGE_PROVIDER: bunny BUNNY_ZONE: ${BUNNY_ZONE} BUNNY_API_KEY: ${BUNNY_API_KEY} BUNNY_CDN_BASE_URL: ${BUNNY_CDN_BASE_URL} BUNNY_TOKEN_AUTH_KEY: ${BUNNY_TOKEN_AUTH_KEY} BUNNY_STORAGE_ENDPOINT: ${BUNNY_STORAGE_ENDPOINT} TRANSACTIONAL_EMAIL_PROVIDER: ${TRANSACTIONAL_EMAIL_PROVIDER} OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME:-language-learning-api} OTEL_EXPORTER_PROMETHEUS_HOST: 0.0.0.0 OTEL_EXPORTER_PROMETHEUS_PORT: ${OTEL_EXPORTER_PROMETHEUS_PORT:-9464} healthcheck: test: [ "CMD-SHELL", 'python -c "import urllib.request; urllib.request.urlopen(''http://localhost:8000/health'')" || exit 1', ] interval: 10s timeout: 5s retries: 10 start_period: 30s depends_on: db: condition: service_healthy restart: unless-stopped deploy: resources: limits: cpus: "1" memory: 1G worker: build: ./api volumes: - ./api:/app:z command: python -m worker.main environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-langlearn}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-langlearn} JWT_SECRET: ${JWT_SECRET} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} DEEPL_API_KEY: ${DEEPL_API_KEY} DEEPGRAM_API_KEY: ${DEEPGRAM_API_KEY} GEMINI_API_KEY: ${GEMINI_API_KEY} PYTHONPATH: /app STORAGE_PROVIDER: bunny BUNNY_ZONE: ${BUNNY_ZONE} BUNNY_API_KEY: ${BUNNY_API_KEY} BUNNY_CDN_BASE_URL: ${BUNNY_CDN_BASE_URL} BUNNY_TOKEN_AUTH_KEY: ${BUNNY_TOKEN_AUTH_KEY} BUNNY_STORAGE_ENDPOINT: ${BUNNY_STORAGE_ENDPOINT} OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME:-language-learning-worker} depends_on: db: condition: service_healthy restart: unless-stopped deploy: resources: limits: cpus: "1" memory: 1G frontend: build: context: ./frontend args: PUBLIC_API_BASE_URL: ${PUBLIC_API_BASE_URL} ports: - "${FRONTEND_PORT:-3001}:3000" environment: ORIGIN: ${ORIGIN} PUBLIC_API_BASE_URL: ${PUBLIC_API_BASE_URL} PRIVATE_JWT_SECRET: ${JWT_SECRET} PRIVATE_DEEPL_API_KEY: ${DEEPL_API_KEY} depends_on: api: condition: service_healthy restart: unless-stopped deploy: resources: limits: cpus: "0.5" memory: 256M prometheus: image: prom/prometheus:v2.54.1 command: - --config.file=/etc/prometheus/prometheus.yml - --storage.tsdb.path=/prometheus volumes: - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheusdata:/prometheus ports: - "127.0.0.1:${PROMETHEUS_PORT:-9090}:9090" depends_on: api: condition: service_healthy restart: unless-stopped deploy: resources: limits: cpus: "0.5" memory: 512M loki: image: grafana/loki:3.1.1 command: -config.file=/etc/loki/loki-config.yml volumes: - ./monitoring/loki/loki-config.yml:/etc/loki/loki-config.yml:ro - lokidata:/loki ports: - "127.0.0.1:${LOKI_PORT:-3100}:3100" restart: unless-stopped deploy: resources: limits: cpus: "0.5" memory: 512M alloy: image: grafana/alloy:v1.7.1 user: "0:0" security_opt: - label=disable command: - run - --server.http.listen-addr=0.0.0.0:12345 - --storage.path=/var/lib/alloy/data - /etc/alloy/config.alloy volumes: - ./monitoring/alloy/config.alloy:/etc/alloy/config.alloy:ro - /var/run/docker.sock:/var/run/docker.sock:ro - alloydata:/var/lib/alloy/data ports: - "127.0.0.1:${ALLOY_PORT:-12345}:12345" depends_on: - loki restart: unless-stopped deploy: resources: limits: cpus: "0.5" memory: 256M grafana: image: grafana/grafana:11.2.0 volumes: - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro - grafanadata:/var/lib/grafana ports: - "127.0.0.1:${GRAFANA_PORT:-3000}:3000" environment: GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?set GRAFANA_ADMIN_PASSWORD} GF_USERS_ALLOW_SIGN_UP: "false" GF_AUTH_ANONYMOUS_ENABLED: "false" depends_on: - prometheus - loki restart: unless-stopped deploy: resources: limits: cpus: "0.5" memory: 512M volumes: pgdata: prometheusdata: grafanadata: lokidata: alloydata: networks: default: name: langlearn