services: # MySQL Database mysql: image: mysql:8.0 container_name: pulse-mysql ports: - "3307:3306" # Using 3307 on host to avoid conflicts environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-pulse_root_password} - MYSQL_DATABASE=${MYSQL_DATABASE:-pulse_db} - MYSQL_USER=${MYSQL_USER:-pulse_user} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-pulse_password} volumes: - mysql-data:/var/lib/mysql - ../backend/db/dev/mysql/mysql-init.sql:/docker-entrypoint-initdb.d/mysql-init.sql:ro - ../backend/db/shared/mysql-default-project-interactions.sql:/docker-entrypoint-initdb.d/includes/mysql-default-project-interactions.sql:ro networks: - pulse-network restart: unless-stopped healthcheck: test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER:-pulse_user}", "-p${MYSQL_PASSWORD:-pulse_password}" ] interval: 10s timeout: 5s retries: 5 start_period: 30s # ═══════════════════════════════════════════════════════════════════════════════ # OpenFGA Database Migration (runs first, creates schema - idempotent) # ═══════════════════════════════════════════════════════════════════════════════ openfga-migrate: image: alpine:3.19 container_name: pulse-openfga-migrate command: [ "/bin/sh", "/scripts/migrate-openfga.sh" ] environment: - OPENFGA_VERSION=1.5.3 - MYSQL_HOST=mysql - MYSQL_PORT=3306 - MYSQL_USER=pulse_user - MYSQL_PASSWORD=pulse_password - MYSQL_DATABASE=openfga volumes: - ./openfga:/scripts:ro depends_on: mysql: condition: service_healthy networks: - pulse-network restart: "no" # ═══════════════════════════════════════════════════════════════════════════════ # OpenFGA Authorization Service (uses MySQL for storage) # ═══════════════════════════════════════════════════════════════════════════════ openfga: image: openfga/openfga:v1.5.3 container_name: pulse-openfga command: run environment: - OPENFGA_DATASTORE_ENGINE=mysql - OPENFGA_DATASTORE_URI=pulse_user:pulse_password@tcp(mysql:3306)/openfga?parseTime=true - OPENFGA_PLAYGROUND_ENABLED=true ports: - "8180:8080" # HTTP API (using 8180 to avoid conflicts) - "8181:8081" # gRPC API - "3001:3000" # Playground UI depends_on: mysql: condition: service_healthy openfga-migrate: condition: service_completed_successfully networks: - pulse-network restart: unless-stopped # Note: No healthcheck - OpenFGA distroless image has no shell/curl # The init script waits for /healthz endpoint directly # OpenFGA Initialization (creates store/model after server is ready - idempotent) openfga-init: image: alpine:3.19 container_name: pulse-openfga-init command: [ "/bin/sh", "/scripts/init-openfga.sh" ] environment: - OPENFGA_URL=http://openfga:8080 volumes: - ./openfga:/scripts:ro depends_on: openfga: condition: service_started # Init script waits for OpenFGA via curl networks: - pulse-network restart: "no" # ClickHouse Database (OTEL data only - traces, logs, metrics) clickhouse: image: clickhouse/clickhouse-server:25.4 container_name: pulse-clickhouse ports: - "8123:8123" # HTTP interface - "9000:9000" # Native client environment: - CLICKHOUSE_DB=${OTEL_CLICKHOUSE_DATABASE:-otel} - CLICKHOUSE_USER=${OTEL_CLICKHOUSE_USER:-pulse_user} - CLICKHOUSE_PASSWORD=${OTEL_CLICKHOUSE_PASSWORD:-pulse_password} - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 volumes: - clickhouse-data:/var/lib/clickhouse - ../backend/db/dev/clickhouse:/docker-entrypoint-initdb.d/:ro networks: - pulse-network restart: unless-stopped healthcheck: test: [ "CMD", "clickhouse-client", "--query", "SELECT 1" ] interval: 10s timeout: 5s retries: 5 start_period: 30s # ClickHouse Table Initializer (runs once to ensure tables exist) clickhouse-init: image: clickhouse/clickhouse-server:25.4 container_name: pulse-clickhouse-init command: /bin/bash /scripts/init-clickhouse.sh volumes: - ./scripts/init-clickhouse.sh:/scripts/init-clickhouse.sh:ro - ../backend/db/dev/clickhouse:/init/clickhouse:ro environment: - CLICKHOUSE_HOST=clickhouse - CLICKHOUSE_USER=${OTEL_CLICKHOUSE_USER:-pulse_user} - CLICKHOUSE_PASSWORD=${OTEL_CLICKHOUSE_PASSWORD:-pulse_password} - CLICKHOUSE_DB=${OTEL_CLICKHOUSE_DATABASE:-otel} networks: - pulse-network depends_on: clickhouse: condition: service_healthy kafka: condition: service_healthy restart: "no" # Kafka (KRaft mode, no Zookeeper) kafka: image: confluentinc/cp-kafka:7.6.0 container_name: pulse-kafka ports: - "9092:9092" environment: CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_LOG_DIRS: /var/lib/kafka/data KAFKA_NUM_PARTITIONS: 12 KAFKA_LOG_RETENTION_HOURS: 1 volumes: - kafka-data:/var/lib/kafka/data networks: - pulse-network restart: unless-stopped healthcheck: test: [ "CMD", "kafka-topics", "--bootstrap-server", "localhost:9092", "--list" ] interval: 5s timeout: 5s retries: 10 start_period: 30s # MinIO (S3-compatible storage for session recordings) minio: image: minio/minio:latest container_name: pulse-minio ports: - "9100:9000" - "9101:9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-pulse_minio} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-pulse_minio_secret} command: server /data --console-address ":9001" volumes: - minio-data:/data networks: - pulse-network restart: unless-stopped healthcheck: test: [ "CMD", "mc", "ready", "local" ] interval: 5s timeout: 5s retries: 5 start_period: 10s # MinIO bucket initializer (session replay + heatmap + symbol files) minio-init: image: minio/mc:latest container_name: pulse-minio-init entrypoint: > /bin/sh -c " mc alias set local http://minio:9000 pulse_minio pulse_minio_secret; mc mb --ignore-existing local/session-recordings; mc mb --ignore-existing local/$${HEATMAP_S3_BUCKET}; mc mb --ignore-existing local/$${SYMBOL_FILES_S3_BUCKET_NAME}; echo 'MinIO buckets session-recordings, heatmap assets, and symbol files ready'; " environment: - HEATMAP_S3_BUCKET=${HEATMAP_S3_BUCKET:-heatmap-assets} - SYMBOL_FILES_S3_BUCKET_NAME=${SYMBOL_FILES_S3_BUCKET_NAME:-pulse-symbol-files} networks: - pulse-network depends_on: minio: condition: service_healthy restart: "no" # Only run once # Session Capture Service (Rust — HTTP ingestion for mobile SDK) pulse-session-capture: build: context: ../backend/session-capture-service dockerfile: Dockerfile container_name: pulse-session-capture ports: - "3400:3400" environment: PORT: "3400" KAFKA_BROKERS: kafka:9092 KAFKA_TOPIC: session_recording_events RUST_LOG: pulse_session_capture=info networks: - pulse-network depends_on: kafka: condition: service_healthy restart: unless-stopped healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:3400/healthcheck" ] interval: 10s timeout: 5s retries: 3 start_period: 10s # Session Replay Ingestion Consumer (Node.js — Kafka → S3 + ClickHouse) pulse-session-replay-ingestion: build: context: ../backend/session-replay-ingestion dockerfile: Dockerfile container_name: pulse-session-replay-ingestion environment: KAFKA_BROKERS: kafka:9092 KAFKA_TOPIC: session_recording_events KAFKA_METADATA_TOPIC: clickhouse_session_replay_events KAFKA_GROUP_ID: session-replay-ingestion S3_ENDPOINT: http://minio:9000 S3_BUCKET: session-recordings S3_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-pulse_minio} S3_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:-pulse_minio_secret} S3_REGION: us-east-1 MAX_BATCH_SIZE_KB: "102400" MAX_BATCH_AGE_MS: "10000" networks: - pulse-network depends_on: kafka: condition: service_healthy minio-init: condition: service_completed_successfully restart: unless-stopped # Heatmap screenshot ingestion (Node.js — Kafka → S3 under heatmap-screenshots/) pulse-heatmap-screenshot-ingestion: build: context: ../backend/heatmap-screenshot-ingestion dockerfile: Dockerfile container_name: pulse-heatmap-screenshot-ingestion environment: KAFKA_BROKERS: kafka:9092 KAFKA_TOPIC: session_recording_events KAFKA_GROUP_ID: heatmap-screenshot-ingestion HEATMAP_S3_ENDPOINT: ${HEATMAP_S3_ENDPOINT:-http://minio:9000} HEATMAP_S3_BUCKET: ${HEATMAP_S3_BUCKET:-heatmap-assets} HEATMAP_S3_REGION: ${HEATMAP_S3_REGION:-us-east-1} HEATMAP_S3_ACCESS_KEY_ID: ${HEATMAP_S3_ACCESS_KEY_ID:-${MINIO_ROOT_USER:-pulse_minio}} HEATMAP_S3_SECRET_ACCESS_KEY: ${HEATMAP_S3_SECRET_ACCESS_KEY:-${MINIO_ROOT_PASSWORD:-pulse_minio_secret}} S3_PREFIX: heatmap-screenshots REDIS_URL: ${REDIS_URL:-} REDIS_HOST: ${REDIS_HOST:-} REDIS_PORT: ${REDIS_PORT:-6379} HEATMAP_QUOTA_PER_DAY: ${HEATMAP_QUOTA_PER_DAY:-5} HEATMAP_QUOTA_TTL_SECONDS: ${HEATMAP_QUOTA_TTL_SECONDS:-172800} HEATMAP_DEDUPE_TTL_SECONDS: ${HEATMAP_DEDUPE_TTL_SECONDS:-172800} networks: - pulse-network depends_on: kafka: condition: service_healthy minio-init: condition: service_completed_successfully restart: unless-stopped otel-collector: image: otel/opentelemetry-collector-contrib:0.137.0 container_name: pulse-otel-collector command: [ "--config=/etc/otel-collector.yaml" ] volumes: - ../backend/ingestion/otel-collector.yaml:/etc/otel-collector.yaml:ro networks: - pulse-network depends_on: clickhouse: condition: service_healthy restart: unless-stopped # Healthcheck disabled: the contrib image is scratch-based (no shell, # wget, or curl). Health is verified externally via :13133. healthcheck: disable: true environment: - CLICKHOUSE_ENDPOINT=tcp://clickhouse:9000 - CLICKHOUSE_DATABASE=${OTEL_CLICKHOUSE_DATABASE:-otel} - CLICKHOUSE_USER=${OTEL_CLICKHOUSE_USER:-pulse_user} - CLICKHOUSE_PASSWORD=${OTEL_CLICKHOUSE_PASSWORD:-pulse_password} ports: - "4317:4317" # OTLP gRPC receiver - "4318:4318" # OTLP HTTP receiver - "8888:8888" # Prometheus metrics (collector internal) - "13133:13133" # Health check # Pulse AI Agent (starts with the stack; set GOOGLE_API_KEY for Gemini — proxy/API may error without it) pulse-ai-agent: build: context: ../pulse_ai dockerfile: Dockerfile container_name: pulse-ai-agent ports: - "8000:8000" environment: - GOOGLE_API_KEY=${GOOGLE_API_KEY:-} - GOOGLE_GENAI_USE_VERTEXAI=${GOOGLE_GENAI_USE_VERTEXAI:-0} - AGENT_MODEL=${AGENT_MODEL:-gemini-2.5-flash} - LOG_LEVEL=${LOG_LEVEL:-INFO} - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-} - PULSE_BASE_URL=${PULSE_BASE_URL:-http://pulse-server:8080} - SESSION_DB_URL=${SESSION_DB_URL:-sqlite:////var/lib/pulse_ai/sessions.db} volumes: - pulse-ai-sessions:/var/lib/pulse_ai - ../pulse_ai/__init__.py:/app/pulse_ai/__init__.py:ro - ../pulse_ai/agent.py:/app/pulse_ai/agent.py:ro - ../pulse_ai/server:/app/pulse_ai/server:ro - ../pulse_ai/constants.py:/app/pulse_ai/constants.py:ro - ../pulse_ai/agents:/app/pulse_ai/agents:ro networks: - pulse-network restart: unless-stopped healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ] interval: 30s timeout: 10s retries: 3 start_period: 15s # Pulse UI Service (React Frontend) pulse-ui: build: context: ../pulse-ui dockerfile: Dockerfile args: REACT_APP_GOOGLE_CLIENT_ID: ${REACT_APP_GOOGLE_CLIENT_ID} REACT_APP_PULSE_SERVER_URL: ${REACT_APP_PULSE_SERVER_URL} REACT_APP_PULSE_WEB_API_KEY: ${REACT_APP_PULSE_WEB_API_KEY:-} REACT_APP_GOOGLE_OAUTH_ENABLED: ${GOOGLE_OAUTH_ENABLED:-false} REACT_APP_ROOT_CAUSE_ENABLED: ${REACT_APP_ROOT_CAUSE_ENABLED:-false} REACT_APP_FIREBASE_API_KEY: ${REACT_APP_FIREBASE_API_KEY} REACT_APP_FIREBASE_AUTH_DOMAIN: ${REACT_APP_FIREBASE_AUTH_DOMAIN} REACT_APP_FIREBASE_PROJECT_ID: ${CONFIG_SERVICE_APPLICATION_GCP_PROJECT_ID} REACT_APP_FIREBASE_STORAGE_BUCKET: ${REACT_APP_FIREBASE_STORAGE_BUCKET:-dummy-project-id.appspot.com} REACT_APP_FIREBASE_MESSAGING_SENDER_ID: ${REACT_APP_FIREBASE_MESSAGING_SENDER_ID:-123456789012} REACT_APP_FIREBASE_APP_ID: ${REACT_APP_FIREBASE_APP_ID} REACT_APP_USE_MOCK_SERVER: ${REACT_APP_USE_MOCK_SERVER} REACT_APP_MOCK_DELAY: ${REACT_APP_MOCK_DELAY} REACT_APP_MOCK_ERROR_RATE: ${REACT_APP_MOCK_ERROR_RATE} REACT_APP_MOCK_LOGGING: ${REACT_APP_MOCK_LOGGING} REACT_APP_FIREBASE_AUTH_EMULATOR: ${REACT_APP_FIREBASE_AUTH_EMULATOR:-false} REACT_APP_FIREBASE_AUTH_EMULATOR_URL: ${REACT_APP_FIREBASE_AUTH_EMULATOR_URL:-http://127.0.0.1:9099} REACT_APP_GCP_MULTI_TENANT_ENABLED: ${REACT_APP_GCP_MULTI_TENANT_ENABLED:-true} container_name: pulse-ui ports: - "3000:8080" environment: - NODE_ENV=production networks: - pulse-network depends_on: pulse-server: condition: service_healthy restart: unless-stopped healthcheck: test: [ "CMD", "wget", "--spider", "-q", "http://127.0.0.1:8080/healthcheck.txt" ] interval: 30s timeout: 10s retries: 3 start_period: 10s # Vector Service (Data Collector) - optional: set VECTOR_ENABLED=true in .env to enable vector: image: 650012445044.dkr.ecr.ap-south-1.amazonaws.com/pulse-vector:latest profiles: - vector container_name: vector command: [ ] environment: - VECTOR_LOG=debug volumes: - ../vector/vector.yaml:/etc/vector/vector.yaml:ro - vector-data:/var/lib/vector networks: - pulse-network restart: unless-stopped healthcheck: test: [ "CMD", "/usr/bin/vector", "validate", "--config-dir", "/etc/vector" ] interval: 10s timeout: 5s retries: 3 start_period: 15s ports: - "8686:8686" - "14317:4317" # OTLP gRPC (host 14317 to avoid conflict with otel-collector on 4317) - "14318:4318" # OTLP HTTP (script sends here to reach Vector) # Pulse Server Service (Java/Vert.x Backend) pulse-server: build: context: ../backend/server dockerfile: Dockerfile container_name: pulse-server ports: - "8080:8080" environment: # AWS Credentials for S3/Athena access (required in .env; use empty for local if unused) - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} # MySQL Database Configuration - MYSQL_WRITER_HOST=mysql - MYSQL_READER_HOST=mysql - MYSQL_PORT=3306 - MYSQL_DATABASE=${MYSQL_DATABASE:-pulse_db} - MYSQL_USER=${MYSQL_USER:-pulse_user} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-pulse_password} - MYSQL_WRITER_MAX_POOL_SIZE=${MYSQL_WRITER_MAX_POOL_SIZE:-10} - MYSQL_READER_MAX_POOL_SIZE=${MYSQL_READER_MAX_POOL_SIZE:-10} # Application Configuration - APP_ENVIRONMENT=${APP_ENVIRONMENT:-dev} # Optional: JVM OTEL export → host observability stack (deploy/observability-otel-prometheus-tempo :14318). Default false = unchanged behavior. - PULSE_BACKEND_OTEL_ENABLED=${PULSE_BACKEND_OTEL_ENABLED:-false} - OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-} - OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-} - OTEL_RESOURCE_ATTRIBUTES=${OTEL_RESOURCE_ATTRIBUTES:-} - CONFIG_SERVICE_APPLICATION_CRONMANAGERBASEURL=${CONFIG_SERVICE_APPLICATION_CRONMANAGERBASEURL:-http://pulse-alerts-cron:4000/cron} - CONFIG_SERVICE_APPLICATION_DASHBOARDBASEURL=${CONFIG_SERVICE_APPLICATION_DASHBOARDBASEURL:-https://app.pulse-ux.com} - CONFIG_SERVICE_APPLICATION_SERVICEURL=${CONFIG_SERVICE_APPLICATION_SERVICEURL:-http://pulse-server:8080} - DASHBOARD_BASE_URL=${DASHBOARD_BASE_URL:-http://localhost:3000} - CONFIG_SERVICE_APPLICATION_GOOGLEOAUTHCLIENTID=${CONFIG_SERVICE_APPLICATION_GOOGLEOAUTHCLIENTID} - CONFIG_SERVICE_APPLICATION_GOOGLEOAUTHENABLED=${GOOGLE_OAUTH_ENABLED:-false} - CONFIG_SERVICE_APPLICATION_JWTSECRET=${CONFIG_SERVICE_APPLICATION_JWTSECRET:-dev-secret-key-at-least-32-characters-long-for-local-testing-only} - CONFIG_SERVICE_APPLICATION_WEBHOOKURL=${CONFIG_SERVICE_APPLICATION_WEBHOOKURL} - DEV_MODE_API_KEY=${DEV_MODE_API_KEY:-default-project_devkey01} - CONFIG_REPLAY_API_BASE_URL=${CONFIG_REPLAY_API_BASE_URL} # Spark Job Configuration - CONFIG_SERVICE_APPLICATION_SPARK_JOB_JAR_PATH=${CONFIG_SERVICE_APPLICATION_SPARK_JOB_JAR_PATH:-s3://pulse-deployment-config/spark/pulse-spark-jobs-1.0-SNAPSHOT.jar} - CONFIG_SERVICE_APPLICATION_SPARK_JOB_FUNNELS_MAIN_CLASS=${CONFIG_SERVICE_APPLICATION_SPARK_JOB_FUNNELS_MAIN_CLASS:-org.dreamhorizon.pulsespark.SparkJobRunner} - CONFIG_SERVICE_APPLICATION_SPARK_JOB_JOURNEYS_MAIN_CLASS=${CONFIG_SERVICE_APPLICATION_SPARK_JOB_JOURNEYS_MAIN_CLASS:-org.dreamhorizon.pulsespark.SparkJobRunner} - CONFIG_SERVICE_APPLICATION_SPARK_JOB_EVENTS_MAIN_CLASS=${CONFIG_SERVICE_APPLICATION_SPARK_JOB_EVENTS_MAIN_CLASS:-org.dreamhorizon.pulsespark.SparkJobRunner} # Product analytics batch engine (conf/analytics-engine-default.conf): spark | clickhouse - ANALYTICS_COMPUTE_ENGINE=${ANALYTICS_COMPUTE_ENGINE:-spark} # Max concurrent per-project ClickHouse batch queries when ANALYTICS_COMPUTE_ENGINE=clickhouse (prod requires > 0) - ANALYTICS_BATCH_PROJECT_CONCURRENCY=${ANALYTICS_BATCH_PROJECT_CONCURRENCY:-1} # ClickHouse Database Configuration - CLICKHOUSE_R2DBC_URL=r2dbc:clickhouse:http://clickhouse:8123/${OTEL_CLICKHOUSE_DATABASE:-otel} - CLICKHOUSE_USERNAME=${OTEL_CLICKHOUSE_USER:-pulse_user} - CLICKHOUSE_PASSWORD=${OTEL_CLICKHOUSE_PASSWORD:-pulse_password} - CLICKHOUSE_HOST=clickhouse - CLICKHOUSE_PORT=8123 - CLICKHOUSE_CLUSTER_NAME=${CLICKHOUSE_CLUSTER_NAME:-} - ENCRYPTION_MASTER_KEY=${VAULT_ENCRYPTION_MASTER_KEY} # Redis Configuration - REDIS_HOST=${REDIS_HOST:-ascend-redis} - REDIS_PORT=${REDIS_PORT:-6379} # S3 Configuration - S3_BUCKET_NAME=${S3_BUCKET_NAME:-pulse-config} - STACK_TRACE_S3_ARCHIVE_ENABLED=${STACK_TRACE_S3_ARCHIVE_ENABLED:-false} - STACK_TRACE_OTEL_S3_BUCKET=${STACK_TRACE_OTEL_S3_BUCKET:-pulse-otel-ingestion} - STACK_TRACE_ARCHIVE_STAGING_DIR=${STACK_TRACE_ARCHIVE_STAGING_DIR:-/tmp/pulse-stack-trace-archive} - STACK_TRACE_ARCHIVE_FLUSH_INTERVAL_MS=${STACK_TRACE_ARCHIVE_FLUSH_INTERVAL_MS:-30000} - S3_REGION=${S3_REGION:-ap-south-1} - S3_ENDPOINT=${S3_ENDPOINT:-} - SYMBOL_FILES_S3_BUCKET_NAME=${SYMBOL_FILES_S3_BUCKET_NAME:-pulse-symbol-files} - CONFIG_DETAILS_S3_FILE_PATH=${CONFIG_DETAILS_S3_FILE_PATH:-config/pulse-config.json} - INTERACTION_DETAILS_S3_FILE_PATH=${INTERACTION_DETAILS_S3_FILE_PATH:-config/interaction-config.json} # Session Replay S3 (MinIO in dev) - SESSION_REPLAY_S3_BUCKET=${SESSION_REPLAY_S3_BUCKET:-session-recordings} - SESSION_REPLAY_S3_ENDPOINT=${SESSION_REPLAY_S3_ENDPOINT:-http://minio:9000} - SESSION_REPLAY_S3_ACCESS_KEY_ID=${SESSION_REPLAY_S3_ACCESS_KEY_ID:-${MINIO_ROOT_USER:-pulse_minio}} - SESSION_REPLAY_S3_SECRET_ACCESS_KEY=${SESSION_REPLAY_S3_SECRET_ACCESS_KEY:-${MINIO_ROOT_PASSWORD:-pulse_minio_secret}} - SESSION_REPLAY_S3_REGION=${SESSION_REPLAY_S3_REGION:-us-east-1} # Heatmap screenshots (dedicated bucket; falls back to session replay bucket if HEATMAP_S3_BUCKET unset) - HEATMAP_S3_BUCKET=${HEATMAP_S3_BUCKET:-heatmap-assets} - HEATMAP_S3_ENDPOINT=${HEATMAP_S3_ENDPOINT:-http://minio:9000} # Browser-facing MinIO S3 port on host is 9100 (9000 on host is ClickHouse). Omit in AWS. - HEATMAP_S3_PRESIGN_ENDPOINT=${HEATMAP_S3_PRESIGN_ENDPOINT:-} - HEATMAP_S3_REGION=${HEATMAP_S3_REGION:-us-east-1} - HEATMAP_S3_ACCESS_KEY_ID=${HEATMAP_S3_ACCESS_KEY_ID:-${MINIO_ROOT_USER:-pulse_minio}} - HEATMAP_S3_SECRET_ACCESS_KEY=${HEATMAP_S3_SECRET_ACCESS_KEY:-${MINIO_ROOT_PASSWORD:-pulse_minio_secret}} - HEATMAP_SCREENSHOTS_S3_PREFIX=${HEATMAP_SCREENSHOTS_S3_PREFIX:-heatmap-screenshots} # CloudFront Configuration - CLOUDFRONT_DISTRIBUTION_ID=${CONFIG_CLOUDFRONT_DISTRIBUTION_ID:-NONE} - CONFIG_CLOUDFRONT_ASSET_PATH=${CONFIG_CLOUDFRONT_ASSET_PATH:-/config/pulse-config.json} - INTERACTION_CLOUDFRONT_ASSET_PATH=${INTERACTION_CLOUDFRONT_ASSET_PATH:-/config/interaction-config.json} # OpenTelemetry Configuration - INTERACTION_CONFIG_URL=${INTERACTION_CONFIG_URL} - LOGS_COLLECTOR_URL=${LOGS_COLLECTOR_URL} - METRIC_COLLECTOR_URL=${METRIC_COLLECTOR_URL} - SPAN_COLLECTOR_URL=${SPAN_COLLECTOR_URL} - CUSTOM_EVENT_COLLECTOR_URL=${CUSTOM_EVENT_COLLECTOR_URL} # Query Engine Configuration - CONFIG_SERVICE_APPLICATION_QUERY_ENGINE=${CONFIG_SERVICE_APPLICATION_QUERY_ENGINE:-athena} # Athena Configuration (only used when QUERY_ENGINE=athena) - CONFIG_SERVICE_APPLICATION_ATHENA_REGION=${CONFIG_SERVICE_APPLICATION_ATHENA_REGION:-ap-south-1} - CONFIG_SERVICE_APPLICATION_ATHENA_DATABASE=${CONFIG_SERVICE_APPLICATION_ATHENA_DATABASE:-pulse_athena_db} - CONFIG_SERVICE_APPLICATION_ATHENA_OUTPUT_LOCATION=${CONFIG_SERVICE_APPLICATION_ATHENA_OUTPUT_LOCATION:-s3://puls-otel-config/} # EMR Serverless (optional; leave unset for local Docker — disabled in emr-serverless-default.conf) - CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_ENABLED=${CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_ENABLED:-false} - CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_REGION=${CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_REGION:-ap-south-1} - CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_APPLICATION_ID=${CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_APPLICATION_ID:-} - CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_EXECUTION_ROLE_ARN=${CONFIG_SERVICE_APPLICATION_EMR_SERVERLESS_EXECUTION_ROLE_ARN:-} # GCP Configuration (only used when QUERY_ENGINE=gcp) - CONFIG_SERVICE_APPLICATION_GCP_PROJECT_ID=${CONFIG_SERVICE_APPLICATION_GCP_PROJECT_ID} # - CONFIG_SERVICE_APPLICATION_GCP_DATASET=${CONFIG_SERVICE_APPLICATION_GCP_DATASET} # OpenFGA Authorization Configuration - OPENFGA_API_URL=${OPENFGA_API_URL:-http://openfga:8080} # OpenFGA will auto-fetch storeId/modelId if enabled and not provided - OPENFGA_STORE_ID=${OPENFGA_STORE_ID:-} - OPENFGA_MODEL_ID=${OPENFGA_MODEL_ID:-} - OPENFGA_ENABLED=${OPENFGA_ENABLED:-true} # pulse-server → pulse-ai-agent on pulse-network; override for host/standalone AI (e.g. host.docker.internal:8000) - AI_SERVICE_URL=${AI_SERVICE_URL:-http://pulse-ai-agent:8000} # Root Cause Analysis (local dev: set ROOT_CAUSE_ENABLED=true to enable) - ROOT_CAUSE_ENABLED=${ROOT_CAUSE_ENABLED:-false} # Track B error attribution: lower for local demos (default 1000 matches prod gate) - ROOT_CAUSE_MIN_POOR_SESSIONS_ERROR_ATTRIBUTION=${ROOT_CAUSE_MIN_POOR_SESSIONS_ERROR_ATTRIBUTION:-1000} # Incident Configuration - INCIDENT_DEFAULT_SLACK_CHANNEL_ID=${INCIDENT_DEFAULT_SLACK_CHANNEL_ID:-} - GOALERT_URL=${GOALERT_URL:-} - GOALERT_API_KEY=${GOALERT_API_KEY:-} - GOALERT_USER_API_KEY=${GOALERT_USER_API_KEY:-} - GOALERT_SERVICE_ID=${GOALERT_SERVICE_ID:-} - GOALERT_SLACK_BOT_TOKEN=${GOALERT_SLACK_BOT_TOKEN:-} # Notification Service Configuration - AWS_REGION=${AWS_REGION:-ap-south-1} - NOTIFICATION_QUEUE_URL=${NOTIFICATION_QUEUE_URL:-} - NOTIFICATION_DLQ_URL=${NOTIFICATION_DLQ_URL:-} - SES_CONFIGURATION_SET_NAME=${SES_CONFIGURATION_SET_NAME:-} - NOTIFICATION_RETRY_MAX_ATTEMPTS=${NOTIFICATION_RETRY_MAX_ATTEMPTS:-3} - NOTIFICATION_RETRY_INITIAL_DELAY_MS=${NOTIFICATION_RETRY_INITIAL_DELAY_MS:-1000} - NOTIFICATION_RETRY_MAX_DELAY_MS=${NOTIFICATION_RETRY_MAX_DELAY_MS:-30000} - NOTIFICATION_RETRY_MULTIPLIER=${NOTIFICATION_RETRY_MULTIPLIER:-2.0} - NOTIFICATION_WORKER_BATCH_SIZE=${NOTIFICATION_WORKER_BATCH_SIZE:-10} - NOTIFICATION_WORKER_VISIBILITY_TIMEOUT=${NOTIFICATION_WORKER_VISIBILITY_TIMEOUT:-60} - NOTIFICATION_WORKER_POLL_INTERVAL=${NOTIFICATION_WORKER_POLL_INTERVAL:-1} - NOTIFICATION_WORKER_ENABLED=${NOTIFICATION_WORKER_ENABLED:-true} - SLACK_CLIENT_ID=${SLACK_CLIENT_ID:-} - SLACK_CLIENT_SECRET=${SLACK_CLIENT_SECRET:-} - SLACK_REDIRECT_URI=${SLACK_REDIRECT_URI:-} - SLACK_UI_REDIRECT_URL=${SLACK_UI_REDIRECT_URL:-} - SLACK_OAUTH_SCOPES=${SLACK_OAUTH_SCOPES:-} # Internal service-to-service auth (pulse-alerts-cron → pulse-server) - CONFIG_SERVICE_APPLICATION_INTERNAL_SERVICE_TOKENS=${CONFIG_SERVICE_APPLICATION_INTERNAL_SERVICE_TOKENS:-} networks: - pulse-network extra_hosts: - "host.docker.internal:host-gateway" depends_on: mysql: condition: service_healthy clickhouse: condition: service_healthy openfga-init: condition: service_completed_successfully pulse-ai-agent: condition: service_healthy restart: unless-stopped healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:8080/healthcheck" ] interval: 30s timeout: 10s retries: 3 start_period: 60s volumes: # Mount config files if needed for runtime configuration - ../backend/server/src/main/resources/config:/app/config:ro # Pulse Alerts Cron Service (Java/Vert.x Cron Manager) pulse-alerts-cron: build: context: ../backend/pulse-alerts-cron dockerfile: Dockerfile container_name: pulse-alerts-cron ports: - "4000:4000" environment: # MySQL Database Configuration - MYSQL_WRITER_HOST=mysql - MYSQL_READER_HOST=mysql - MYSQL_DATABASE=${MYSQL_DATABASE:-pulse_db} - MYSQL_USER=${MYSQL_USER:-pulse_user} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-pulse_password} # Application Configuration - CONFIG_SERVICE_APPLICATION_PULSESERVERURL=${CONFIG_SERVICE_APPLICATION_PULSESERVERURL:-http://pulse-server:8080} - PULSE_BACKEND_OTEL_ENABLED=${PULSE_BACKEND_OTEL_ENABLED:-false} - OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-} - OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-} - OTEL_RESOURCE_ATTRIBUTES=${OTEL_RESOURCE_ATTRIBUTES:-} - CONFIG_SERVICE_APPLICATION_WEBHOOKURL=${CONFIG_SERVICE_APPLICATION_WEBHOOKURL} - CONFIG_SERVICE_APPLICATION_SERVICEJWTSECRET=${CONFIG_SERVICE_APPLICATION_SERVICEJWTSECRET:-dev_service_jwt_secret} - CONFIG_SERVICE_APPLICATION_CLICKHOUSEHOST=clickhouse - CONFIG_SERVICE_APPLICATION_CLICKHOUSEPORT=8123 - CONFIG_SERVICE_APPLICATION_CLICKHOUSEDATABASE=otel - CONFIG_SERVICE_APPLICATION_CLICKHOUSEUSERNAME=pulse_user - CONFIG_SERVICE_APPLICATION_CLICKHOUSEPASSWORD=pulse_password - CONFIG_SERVICE_APPLICATION_REDISHOST=${REDIS_HOST:-redis} - CONFIG_SERVICE_APPLICATION_REDISPORT=${REDIS_PORT:-6379} # Batch job configuration - CONFIG_SERVICE_APPLICATION_BATCHFUNNELSENDPOINT=${CONFIG_SERVICE_APPLICATION_BATCHFUNNELSENDPOINT:-/internal/analytics/funnels} - CONFIG_SERVICE_APPLICATION_BATCHJOURNEYSENDPOINT=${CONFIG_SERVICE_APPLICATION_BATCHJOURNEYSENDPOINT:-/internal/analytics/journeys} - CONFIG_SERVICE_APPLICATION_BATCHEVENTSENDPOINT=${CONFIG_SERVICE_APPLICATION_BATCHEVENTSENDPOINT:-/internal/analytics/events} - CONFIG_SERVICE_APPLICATION_BATCHSCHEDULETIME=${CONFIG_SERVICE_APPLICATION_BATCHSCHEDULETIME:-02:00} - CONFIG_SERVICE_APPLICATION_BATCHJOBSENABLED=${CONFIG_SERVICE_APPLICATION_BATCHJOBSENABLED:-true} networks: - pulse-network extra_hosts: - "host.docker.internal:host-gateway" depends_on: mysql: condition: service_healthy pulse-server: condition: service_healthy minio-init: condition: service_completed_successfully restart: unless-stopped healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:4000/healthcheck" ] interval: 30s timeout: 10s retries: 3 start_period: 60s volumes: # Mount config files if needed for runtime configuration - ../backend/pulse-alerts-cron/src/main/resources/config:/app/config:ro # Pulse S3 Archiver (Kafka → Parquet → S3 mirror of ClickHouse tables) pulse-s3-archiver: build: context: ../backend/pulse-s3-archiver dockerfile: Dockerfile container_name: pulse-s3-archiver environment: - KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} - S3_BUCKET=${S3_BUCKET:-pulse-otel-ingestion} - S3_REGION=${S3_REGION:-ap-south-1} - S3_ENDPOINT=${S3_ENDPOINT:-} - AWS_PROFILE=${AWS_PROFILE:-} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} - ARCHIVER_FLUSH_INTERVAL_MS=${ARCHIVER_FLUSH_INTERVAL_MS:-30000} - ARCHIVER_STAGING_DIR=/tmp/pulse-archiver - PULSE_BACKEND_OTEL_ENABLED=${PULSE_BACKEND_OTEL_ENABLED:-false} networks: - pulse-network extra_hosts: - "host.docker.internal:host-gateway" depends_on: kafka: condition: service_healthy restart: unless-stopped healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:8080/healthcheck" ] interval: 30s timeout: 10s retries: 3 start_period: 60s networks: pulse-network: driver: bridge volumes: mysql-data: driver: local clickhouse-data: driver: local vector-data: driver: local pulse-ai-sessions: driver: local kafka-data: driver: local minio-data: driver: local